嵌入式Linux应用开发基础知识(六)——Makefile实例
前面我们学了很多Makefile相关的知识,但是没有写过一个完整的代码,这一章我们写出一个实例
一、完善Makefile
在之前我们写了一个较为完善的Makefile程序,但是还是存在一些问题,我们需要慢慢完善程序。相关程序如下:
main.c
#include <stdio.h>
#include "sub.h"int main(int argc, char *argv[])
{int i;printf("Main fun!\n");sub_fun();return 0;
}
sub.c
#include "sub.h"void sub_fun(void)
{printf("Sub fun! A=%d\n", A);
}
sub.h
#define A 2void sub_fun(void);
Makefile
test : main.o sub.ogcc -o test $^
%.o : %.cgcc -c -o $@ $<
clean:rm *.o test.PHONY: clean
运行结果:
由上述程序可知,sub.c中使用了A的宏命令,我们在sub.h改变A的值,再次编译:
发现提示:test没有更新,这是为什么呢?
这是因为我们在Makefile中没有依赖sub.h,无论sub.h 怎么变化,sub.o都不会改变,导致test没有更新,那么我们应该在Makefile中加入依赖代码,如下:
test : main.o sub.ogcc -o test $^
sub.o : sub.c sub.h
%.o : %.cgcc -c -o $@ $<
clean:rm *.o test.PHONY: clean
当A改为3时,运行结果为:
那么每次新增了头文件,是不是得我们手动添加呢?显然这样是不高效的,C/C++的编译器提供了自动获取头文件依赖的方法。
二、自动获取头文件的依赖关系
1. 基础知识
1.1 -M选项
GCC的-M选项生成文件的依赖关系,同时也把一些标准库的头文件包含了进来。本质是告诉预处理器输出一个适合 make 的规则,用于描述各目标文件的依赖关系。对于每个源文件,预处理器输出 一个 make 规则,该规则的目标项 (target) 是源文件对应的目标文件名,依赖项 (dependency) 是源文件中 “#include” 引用的所有文件,生成的规则可以是单行,但如果太长,就用’'换行符续成多行。规则显示在标准输出,不产生预处理过的 C 程序。注意:该选项默认打开了 -E 选项, -E 参数的用处是使得编译器在预处理结束时就停止编译。
例如,执行下面的命令:
gcc -M main.c
输出如下:
由编译器自动生成依赖关系,这样做的好处有以下几点:
- 不必手动书写若干目标文件的依赖关系,由编译器自动生成
- 不管是源文件还是头文件有更新,目标文件都会重新编译
1.2 -MM选项
生成文件的依赖关系,和 -M 类似,但不包含标准库的头文件
例如:
···
gcc -MM main.c
···
输出结果:
1.3 -MG选项
要求把缺失的头文件按存在对待,并且假定他们和源文件在同一目录下,必须和 ‘-M’ 选项一起用。
1.4 -MF File
必须和 ‘-M’ 或’-MM’选项一起用,当使用了 “-M” 或者 “-MM” 选项时,则把依赖关系写入名为 “File” 的文件中。若同时也使用了 “-MD” 或 “-MMD”,“-MF” 将覆写输出的依赖文件的名称 。
例如:
gcc -M -MF main.d main.c
输出结果:
则 “-M” 输出的内容就保存在 main.d 文件中了。
1.5 -MD或者-MMD
等同于 -M -MF File(或者 -MM -MF File),但是默认关闭了 -E 选项。其输出的文件名是基于 -o 选项,若给定了 -o 选项,则输出的文件名是 -o 指定的文件名,并添加 .d 后缀,若没有给定,则输入的文件名作为输出的文件名,并添加 .d 后缀,同时继续指定的编译工作。
注意:-MD 不会像 -M 那样阻止正常的编译任务,因为它默认关闭了 -E 选项,比如命令中使用了 -c 选项,其结果要生成 .o 文件,若使用了 -M 选项,则不会生成 .o 文件,若使用的是 -MD 选项,则会生成 .o 文件,有没有关闭-E,只要看窗口是否输出编译信息。
例如:
gcc -E -MD main.c
输出:
本目录下生成了以下文件:
main.d
同时在终端上输出了 main.c 文件的预处理结果
例如:
gcc -E -o tmp.i -MD main.c
本目录下生成了以下文件:
tmp.d tmp.i
例如:
gcc -c -MD main.c
本目录下生成了以下文件:
main.d main.o
例如:
gcc -c -o tmp.o -MD main.c
本目录下生成了以下文件:
tmp.d tmp.o
2. 继续优化Makefile
如果我们想编译生成main.o的同时且把依赖写入到main.d,代码如下:
gcc -c -o main.o main.c -MD -MF main.d
输出如下:
我们继续优化Makefile,一般.d文件可以不用被用户看到,我们设置为隐藏(Linux如果要将一个文件隐藏,只需在前面加上. , 此时使用ls,或者ls -l查看不到,只能使用ls -a才能查看到)
优化后的Makefile如下:
test : main.o sub.ogcc -o test $^
sub.o : sub.c sub.h
%.o : %.cgcc -c -o $@ $< -MD -MF .$@.d
clean:rm *.o test.PHONY: clean
运行结果图:
可以看到生成我们需要的 .main.o.d .sub.o.d文件
3. 比较完善的Makefile
实例代码如下:
objs = main.o sub.odep_files := $(patsubst %, .%.d, $(objs))
dep_files := $(wildcard $(dep_files))test : $(objs)gcc -o test $^ifneq ($(dep_files),)
include $(dep_files)
endif%.o : %.cgcc -c -o $@ $< -MD -MF .$@.d
clean:rm *.o testdistclean:rm $(dep_files)
.PHONY: clean
运行结果:
代码解释:
- 为了方便书写依赖的对象文件,程序使用objs 即时变量存储OBJ文件
- dep_files := $(patsubst %, .%.d, $(objs))这段代码是将main.o sub.o变为.main.o.d .sub.o.d
- test : $(objs) 这是直接使用objs对象进行编译
- ifneq endif是Makefile的if条件编译语句
- ifneq ($(dep_files),)表示如果dep_files为空,则条件为真,逗号后面有啥都没有表示空
- include 语句和C语言的include语句类似,都是包含相应的头文件,使其编译
- distclean标签用于清除.x.o.d文件
三、CFLAGS选项
CFLAGS 表示用于 C 编译器的选项,可以搭配各种选项达到不同的作用,现在我们介绍两种选项
1. -Werror 选项
该选项表示将所有警告当做错误进行处理,在程序开发时,建议采用这种方式,因为很多错误是由于警告引起来的,警告我们也不能忽视,如下为添加该选项后的Makefile文件代码
objs = main.o sub.odep_files := $(patsubst %, .%.d, $(objs))
dep_files := $(wildcard $(dep_files))CFLAGS = -Werrortest : $(objs)gcc -o test $^ifneq ($(dep_files),)
include $(dep_files)
endif%.o : %.cgcc -c -o $@ $< -MD -MF .$@.d
clean:rm *.o testdistclean:rm $(dep_files)
.PHONY: clean
2. -Idir 选项
-Idir选项可以指定编译器查找头文件的路径,加入这个选项后,我们用户定义的头文件就可以使用尖括号进行包含了,我们在根目录下新建include文件夹,并把所有的头文件放在这里,则Makefile就可以这么写了:
objs = main.o sub.odep_files := $(patsubst %, .%.d, $(objs))
dep_files := $(wildcard $(dep_files))CFLAGS = -Werror -Iincludetest : $(objs)gcc -o test $^ifneq ($(dep_files),)
include $(dep_files)
endif%.o : %.cgcc -c -o $@ $< -MD -MF .$@.d
clean:rm *.o testdistclean:rm $(dep_files)
.PHONY: clean
嵌入式Linux应用开发基础知识(六)——Makefile实例相关推荐
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED驱动框架--面向对象、分层设计思想
文章目录 前言 1.LED驱动程序框架 1.1.对于LED驱动,我们想要什么样的接口? 1.2.LED驱动要怎么写,才能支持多个板子?分层写 1.3.程序分析 驱动程序 应用程序 Makefile 1 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之第一个驱动
文章目录 前言 1.Hello驱动 1.1.APP打开的文件在内核中如何表示? 1.2.打开字符设备节点时,内核中也有对应的struct file 1.3.如何编写驱动程序? 1.4.驱动程序代码 1 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之Pinctrl子系统和GPIO子系统的使用
文章目录 前言 1.Pinctrl子系统 1.1.为什么有Pinctrl子系统 1.2.重要的概念 1.3.代码中怎么引用pinctrl 2.GPIO子系统 2.1.为什么有GPIO子系统 2.2.在 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架
文章目录 前言 1.APP怎么读取按键值 1.1.查询方式 1.2.休眠-唤醒方式 1.3.poll方式 1.3.异步通知方式 1.5. 驱动程序提供能力,不提供策略 2.按键驱动程序框架--查询方式 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED模板驱动程序的改造:设备树
文章目录 前言 1.驱动的三种编写方法 2.怎么使用设备树写驱动程序 2.1.设备树节点要与platform_driver能匹配 2.2.修改platform_driver的源码 3.实验和调试技巧 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之设备树模型
文章目录 前言 1.设备树的作用 2.设备树的语法 2.1.设备树的逻辑图和dts文件.dtb文件 2.1.1.1Devicetree格式 1DTS文件的格式 node的格式 properties的格 ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之总线设备驱动模型
文章目录 前言 1.驱动编写的三种方法 1.1.传统写法 1.2.总线驱动模型 1.3.设备树驱动模型 2.Linux实现分离:Bus/Dev/Drv模型 2.1.Bus/Dev/Drv模型 2.2. ...
- 【嵌入式Linux】嵌入式Linux驱动开发基础知识之驱动设计的思想:面向对象/分层/分离
文章目录 前言 1.分离设计 驱动程序分析---程序分层 通用驱动程序---面向对象 个性化驱动程序---分离 APP 程序分析 前言 韦东山嵌入式Linux驱动开发基础知识学习笔记 文章中大多内容来 ...
- 【嵌入式Linux】嵌入式Linux应用开发基础知识之I2C应用编程和SMBus协议及AP3216C应用编程
文章目录 前言 1.IIC协议和SMBUS协议 1.1.IIC协议 1.1.1.硬件框架 1.1.2.软件框架 1.1.3.读写数据格式 1.1.4.硬件结构--在硬件上是如何实现双向传输 1.2.S ...
最新文章
- 四川第七届 I Travel(bfs)
- 【C++】C++命名空间重定向
- 调整php-fpm,nginx调整php-fpm
- 大咖说:出道十五载,认知五迭代
- vue当前浏览器是否为ie_Vue进阶(六十八):JS-判断当前浏览器是否为IE
- 互斥量和信号量的区别
- Introduce Null Object(引入Null对象)
- as 运算符 与 where T : class
- 数据预处理第3讲:归一化与离散化
- 《python3网络爬虫开发实战》学习笔记:pyspider报错Exception: HTTP 599: SSL certificate problem...
- 电脑系统声音常见故障问题解决方法汇总
- Variable used in lambda expression should be final or effectively final
- 【Pytorch with fastai】第 11 章 :使用 fastai 的中级 API 进行数据处理
- HMAC_SHA1和SHA1的区别
- 观点动力学模型:主要理论与模型综述
- 【ct107d】蜂鸣器,继电器控制
- 关于高精度交流恒流源设计是怎样的?
- MacBook Pro外接显示器竖屏显示
- linux如何设置mac快捷键,Mac与Linux常用快捷键汇总
- pycharm远程调试多个项目出现(Permission denied)