第4篇:嵌入式Linux应用开发基础知识
我们在VS中可以轻而易举地编译并运行代码,其背后的原理即为Makefile,接下来我将详细总结与整理韦老师所讲的Makefile使用技巧。
1、当工程中遇到成百上千的文件同时编译时,如果修改了其中一个源文件,那么当你重新使用gcc -o直接编译生成应用程序时,会耗费大量时间,使用makefile的话,可以自定义编译规则和步骤,如果只有部分文件被修改,那么只用编译部分文件然后再链接即可。
2、能节约敲命令的时间,只用打出make命令就能直接编译链接
目标文件:依赖文件
命令
例一:
test:a.o
gcc -o test a.o
a.o:a.c
gcc -c -o a.o a.c
b.o:b.c
gcc -c -o b.o b.c
当“目标文件”不存在
或者
某个依赖文件比目标文件新,则
执行命令
%.o:表示所用的.o文件
%.c:表示所有的.c文件(注意Linux命令行中的通配符是*)
$@:表示目标
$<:表示第1个依赖文件
$^:表示所有依赖文件
@:加在命令前面可以在make的过程中不显示该命令
例二:
test:a.o b.o
gcc -o $@ $^
%.o:%.c
gcc -c -o $@ $<
a、我们想清除文件,我们在Makefile的结尾添加如下代码就可以了:
clean:
rm *.o test
*1)执行 make :生成第一个可执行文件。
*2)执行 make clean : 清除所有文件,即执行: rm *.o test。
make后面可以带上目标名,也可以不带,如果不带目标名的话它就想生成第一个规则里面的第一个目
标。
b、但是上面这个写法有个问题,就是如果文件夹中有clean文件,那么就无法执行rm命令,并且会出现以下提示:
make: `clean’ is up to date.
这是因为makefile 的规则是:1、目标文件不存在 2、依赖文件更新
这种情况下目标文件已经存在而且没有依赖文件,make clean就不会执行rm命令
解决办法:在makefile文件中添加语句
.PHONY:clean # 把clean变成假想目标,这样就不会去判断clean文件是否存在
clean:
rm *.o test
.PHONY:clean # 把clean变成假想目标,这样就不会去判断clean文件是否存在
在makefile中有两种变量:简单变量(即时)和延时变量
在定义时即确定
A := 123 # A的值被确定,在定义时即确定
在变量被第一次使用的时候才会变确定
B = 456 # B的值使用到时才确定
想使用变量的时候需要用$符号进行引用。
当我们执行make命令的时候,make这个指令本身,会把整个Makefile读进去,进行全部
分析,然后解析里面的变量。常用的变量的定义如下:
= # 延时变量
:= # 即时变量
?= # 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略这句
+= # 附加, 它是即时变量还是延时变量取决于前面的定义
?:= #如果这个变量在前面已经被定义了,这句话就会不会起效果,
例三:
A := $(C)
B = $(C)
C = abc
#D = 100ask
D ?= weidongshan
all:
@echo A = $(A)
@echo B = $(B)
@echo D = $(D)
C += 123
运行结果:
可以看到echo命令被隐藏了;A的值在定义时C的值没有被定义,所以结果为空;因为makefile是被整体读进去识别之后再执行命令,所以B的值被覆盖后的C值所定义;D的值之前没有被定义,如果被定义了则以第一次被定义时为准。
函数foreach语法如下:
$(foreach var,list,text)
前两个参数,‘var’和‘list’,将首先扩展,注意最后一个参数 ‘text’ 此时不扩展;接着,对每一个 ‘list’ 扩
展产生的字,将用来为 ‘var’ 扩展后命名的变量赋值;然后 ‘text’ 引用该变量扩展;因此它每次扩展都不
相同。结果是由空格隔开的 ‘text’。在 ‘list’ 中多次扩展的字组成的新的 ‘list’。‘text’ 多次扩展的字串联起
来,字与字之间由空格隔开,如此就产生了函数 foreach 的返回值。
实例:
A = a b c
B = $(foreach x,$(A),$(x).o) #把a,b,c变成a.o b.o c.o
all:
@echo B = $(B)
语法规则
$(filter pattern...,text) # 在text中取出符合patten格式的值
$(filter-out pattern...,text) # 在text中取出不符合patten格式的值
C = a b c d/
D = $(filter %/,$(C))
E = $(filter-out %/,$(C))
all:
@echo D=$(D)
@echo E=$(E)
用于取出该文件下符合某种格式的变量(文件),比如可以取出所有后缀为.c的文件。语法:
$(wildcard pattern) # pattern定义了文件名的格式, wildcard取出符合该格式的文件。
例子:
#首先在文件下面新建a.c b.c a.o b.o
files = $(wildcard *.c) # 注意!!!这里是对文件夹中的文件进行操作,所以用*作为通配符(如果是对Makefile文件中的变量做操作,则使用%作为通配符)
all:
@echo files = $(files)
结果:
我们也可以用wildcard函数来判断,真实存在的文件
结果:
·files3 = a.c b.c c.c
函数 patsubst 语法如下:
$(patsubst pattern, replacement, $(var)) #把var列表中符合pattern格式的值替换成replacement的值
例子:
files2 = a.c b.c c.c d.c e.c abc
dep_files = $(patsubst %.c,%.d,$(files2)) #把.c 文件的后缀都改为 .d
all:
@echo dep_files = $(dep_files)
结果
dep_files = a.d b.d c.d d.d e.d abc
(以下内容参考了CSDN博主「Jerry.yl」的原创文章,原文链接:https://blog.csdn.net/QQ1452008/article/details/50855810)
如果是一个比较大型的工程,我们必需清楚每一个源文件都包含了哪些头文件,并且在加入或删除某些头文件时,也需要一并修改 Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情,可以使用 C/C++ 编译器的 “-M” 选项,即自动获取源文件中包含的头文件,并生成一个依赖关系。例如,执行下面的命令:
gcc -M main.c
其输出:main.o : main.c defs.h
这样一来通过编译器输出依赖文件的好处是
.d文件就是编译器使用了-MD选项后生成的写有源文件需要的依赖文件的文件
有了.d文件就可以很方便的使用makefile了
e.g.
main.d:保存了 main.o 依赖关系的文件
gcc -M c.c // 打印出依赖
gcc -M -MF c.d c.c // 把依赖写入文件c.d
gcc -c -o c.o c.c -MD -MF c.d // 编译c.o, 把依赖写入文件c.d
具体参考原文第二部分:https://blog.csdn.net/QQ1452008/article/details/50855810
SRCS=$(wildcard *.c) #SRCS意为源文件sources,用延迟赋值为文件夹中所有的.c文件名作为变量
OBJS=$(SRCS:.c=.o) #OBJS意为目标文件objects,将SRCS中的.c变量替换为.o
DEPS=$(SRCS:.c=.d) #DEPS意为依赖文件,同理替换为.d
.PHONY: all clean #虚拟目标,假想
all: main
-include $(DEPS) #注释:'-'号的作用:加载错误时,会继续执行 make,
#主要是考虑到首次 make 时,目录中若不存在 '*.d' 文件时,
#加载便会产生错误而停止 make 的执行
%.o:%.c
gcc -c -g -Wall $< -o $@ -MD -MF $*.d -MP #注释:
#$* 表示目标模式中 '%' 及其之前的部分.如果目标是 'dir/a.foo.b',
#并且目标的模式为 'a.%.b',那么 '$*' 的值就是 'dir/a.foo'。
#如果目标中没有模式的定义,那么 '$*' 就不能被推导出;
#但是,如果目标文件是 make 所识别的,那么 '$*' 就是除了后缀的那一部分。
#例如:目标是 'foo.c',因为 '.c' 是 make 所能识别的后缀名,
#所以 '$*' 的值就是 'foo',这个特性是 GNU make 的。
main: $(OBJS)
gcc $^ -o $@ #注释:$^:表示所有的依赖文件 $@:表示目标文件
clean:
rm -f *.d *.o main
即编译参数。
例如可以加上
CFLAGS = -Werror -Iinclude # -Iinclude中第一个字幕是大写i而不是小写L
-Werror 表示把所有的警告当作错误
-I 可以指定头文件路径,例如 -I/xx/xx/xxx
-include用来包含头文件,但一般情况下包含头文件都在源码里用#include xxxxxx实现,-include参数很少用。
-Iinclude 表示将当前文件夹中的include文件夹作为第一个寻找头文件的目录(需要在当前文件夹中新建一个include文件夹用于存放所有的include文件)
-l 用于指定链接的库。例如-lm 指定连接数学库math,-lxxx就是去链接名字为xxx的库名
小tips:库的文件名通常为libxxx.so,这个xxx就是库名,例如math库的文件名叫libm.so,掐头去尾去掉lib和.so剩下的就是库名
而-L用于链接库文件所在的目录,通常用于链接自己安装不在默认路径下的库,直接加上路径即可。(库的默认路径通常为/usr/bin/ld)
更多好用的编译选项可以通过gcc文档去查找,与makefile本身没有关系
易犯错误:
1、空命令前有tab,导致报错