Makefile的基本使用
Makefile的介绍和规则
问:之前使用单片机开发的时候,使用Keil工具程序点击一下鼠标就可以进行编译了,那么它为什么可以这样如此轻松进行编译?如何组织管理大量的工程文件?如何确定编译哪一个文件?
答:实际上windows工具管理程序的内部机制,也是Makefile。当我们在Linux下来开发裸板程序的时候,使用Makefile组织管理这些程序,这些文件。
程序的编译和链接过程
一个C/C++文件要经过预处理(preprocessing)、编译(compilation)、汇编(assembly)和链接(linking)等4步才能变成可执行文件。
一般来说,我们会把前三个步骤即预处理、编译和汇编统称为编译。
编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件(.h 文件)中应该只是声明,而定义应该放在C/C++文件(.c 文件)中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。
链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File)。在大多数的时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“*库文件”(Library File)*,也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。
总结一下,源文件首先会生成中间目标文件(O文件或是OBJ文件),再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error)。
Makefile的介绍
What:什么是Makefile?
在 Linux(unix )环境下使用GNU 的make工具能够比较容易的构建一个属于你自己的工程,整个工程的编译只需要一个命令就可以完成编译、连接以至于最后的执行。不过这需要我们投入一些时间去完成一个或者多个称之为Makefile 文件的编写。
所要完成的Makefile 文件描述了整个工程的编译、连接等规则。其中包括:工程中的哪些源文件需要编译以及如何编译、需要创建哪些库文件以及如何创建这些库文件、如何最后产生我们想要的可执行文件。
make是一个命令工具,它解释Makefile 中的指令。在Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。Makefile 有自己的书写格式、关键字、函数。像C 语言有自己的格式、关键字和函数一样。
make命令执行的时候,需要一个Makefile文件(此文件不需要任何后缀),这个Makefile文件就告诉了make命令如何去编译和链接程序。后续就能够使用一个make命令就能快速自动对某些文件进行重编译和链接目标程序。而要想快速编译和链接,就需要写好Makefile文件。
注意:Makefile文件有以下的书写规则:
1.如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
2.如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
3.如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。
Why:为什么使用Makefile?
编写一个Makefile文件尽管看起来可能是很复杂的事情,但是为工程编写Makefile 的好处是能够使用一行命令来完成“自动化编译”,一旦提供一个(通常对于一个工程来说会是多个)正确的 Makefile。编译整个工程你所要做的事就是在shell 提示符下输入make命令。整个工程完全自动编译,极大提高了效率。
Makefile的规则
Makefile最基本的语法就是规则,规则如下:
目标 : 依赖1 依赖2 ...
[TAB]命令
当“依赖”比“目标”新,执行它们下面的命令。这就是Makefile最基本的语法规则。
下面,将写一个程序来实验一下:
文件a.c
02 #include <stdio.h>
03
04 int main()
05 {
06 func_b();
07 return 0;
08}
文件b.c
2 #include <stdio.h>
3
4 void func_b()
5 {
6 printf("This is B\n");
7 }
编译:
gcc -o test a.c b.c
运行:
./test
结果:
It is B
gcc -o test a.c b.c 这条命令虽然简单,但是它完成的功能不简单。
(提醒:gcc -o test a.c b.c -v :加上一个**‘-v’**选项可以看到它们的处理过程,包括编译和链接的过程,include的路径等等信息。)
从上文的程序的编译和链接就可以知道 .c 程序—> 可执行程序要按顺序经过四个步骤:预处理、编译、汇编、;链接。对gcc -o test a.c b.c 命令进行具体分析,可知:
①对于a.c:执行:预处理 编译 汇编 的过程,a.c ==>xxx.s ==>xxx.o 文件。
②对于b.c:执行:预处理 编译 汇编 的过程,b.c ==>yyy.s ==>yyy.o 文件。
③最后:xxx.o和yyy.o链接在一起得到一个test应用程序。
第一次编译 a.c 得到 xxx.o 文件,这是很合乎情理的。
执行完第一次之后,如果修改 a.c 又再次执行:gcc -o test a.c b.c,这一条命令,对于 a.c 应该重新生成 xxx.o,但是对于 b.c 又会重新编译一次,这完全没有必要,因为b.c 根本没有修改,直接使用第一次生成的 yyy.o 文件就可以了。
缺点:使用gcc -o test a.c b.c 则会对所有的文件都会再处理一次,即使 b.c 没有经过修改,b.c 也会重新编译一次,当文件较少时,这还没有什么问题。当文件非常多的时候,就会导致效率太低,编译时间过长。如果文件非常多的时候,我们,若仅仅只修改了少量的文件,但所有的文件都会重新处理一次,编译的时候就会等待很长时间。
解决方法:对于这些源文件,我们应该分别处理,执行:预处理 编译 汇编,先分别编译它们,最后再把它们链接在一起,比如:
编译:
gcc -o a.o a.c
gcc -o b.o b.c
链接:
gcc -o test a.o b.o
比如:上面的例子,当我们修改a.c之后,a.c会重现编译,得到新的a.o 文件,然后再把新的a.o 和 旧的 b.o 文件链接在一起就可以了。而b.c就不需要重新编译,节省时间了。
那么问题又来了,怎么知道哪些文件被更新了/被修改了?
比较时间,比较 a.o 和 a.c 的时间,如果a.c的时间比 a.o 的时间更加新的话,就表明 a.c 被修改了,同理b.o和b.c也会进行同样的比较。比较test和 a.o,b.o 的时间,如果a.o或者b.o的时间比test更加新的话,就表明应该重新生成test。而由Makefile的基本书写规则得知,它就是这样做的。
How:如何使用Makefile?
写一个基本的Makefile:
根据Makefile的基本规则:
目标 : 依赖1 依赖2 ...
[TAB]命令
当“依赖”比“目标”新,就执行它们下面的命令。我们要把上面三个命令写成makefile规则,如下:
test :a.o b.o #test是目标,它依赖于a.o b.o文件,一旦a.o或者b.o比 #test新的时候,就需要执行下面的命令,重新生成test可执行程序。
gcc -o test a.o b.o
a.o : a.c #a.o依赖于a.c,当a.c更加新的话,执行下面的命令来生成a.o
gcc -c -o a.o a.c
b.o : b.c #b.o依赖于b.c,当b.c更加新的话,执行下面的命令,来生成b.o
gcc -c -o b.o b.c
我们来作一下实验:
在a.c 文件和b.c 文件所在的目录下,建立一个Makefile文件(注意不添加任何后缀):
文件:Makefile
1 test:a.o b.o
2 gcc -o test a.o b.o
3
4 a.o : a.c
5 gcc -c -o a.o a.c
6
7 b.o : b.c
8 gcc -c -o b.o b.c
上面是Makefile中的三条规则。Makefile,就是名字为“Makefile”的文件。
当我们想编译程序时,直接执行make命令就可以了。
一执行make命令它想生成第一个目标test可执行程序。如果发现a.o 或者b.o没有,就要先生成a.o或者b.o,发现a.o依赖a.c,有a.c但是没有a.o,他就会认为a.c比a.o新,就会执行它们下面的命令来生成a.o,同理b.o和b.c的处理关系也是这样的。
我们第一次执行make的时候,所有命令都执行,由Makefile第一条书写规则可知(如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接)
我们再次执行make就会判断Makefile文件中的依赖,发现依赖没有更新,所以目标文件就不会重现生成,就会有上面的提示“make: `test’ is up to date.”
若只是修改了a.c 文件,a.c文件就比a.o文件更加新,所以就会执行gcc -c -o a.o a.c 来更新a.o文件。而a.o比test新,所以又要使用gcc -o test a.o b.o 来重新链接生成test可执行程序。
Makefile的核心就是其里面的规则!!!
执行make命令的时候,就会在当前目录下面找到名字为:Makefile的文件,根据里面的内容来执行里面的判断/命令。
Makefile的基础语法
在这里仅仅只是展示最基础语法,若想深入了解Makefile,
可学习官方文档: http://www.gnu.org/software/make/manual/
或查阅书籍:GNU Make 使用手册(中译版)https://file.elecfans.com/web1/M00/7D/E7/o4YBAFwQthSADYCWAAT9Q1w_4U0711.pdf
基础语法之通配符
what:什么是通配符?
通配符是一种特殊语句,主要有星号(*)和问号(?),用来模糊搜索文件。当查找文件夹时,可以使用它来代替一个或多个真正字符;当不知道真正字符或者懒得输入完整名字时,常常使用通配符代替一个或多个真正的字符。
why:为什么要使用通配符?
假如一个目标文件所依赖的依赖文件很多,那样岂不是我们要写很多规则,这显然是不合乎常理的,而且十分麻烦,所以我们可以使用通配符,来解决这些问题。
how:如何使用通配符?
基本的通配符:
%.o:表示所用的.o文件
%.c:表示所有的.c文件
$@:表示目标文件
$<:表示第1个依赖文件
$^:表示所有依赖文件
$*: 表示目标文件的名称,不包含扩展名
$?: 依赖项中,所有比目标文件新的依赖文件
在上面的程序基础下,在该目录下增加一个 c.c 文件,代码如下:
#include <stdio.h>
void func_c()
{
printf("This is C\n");
}
然后在main函数中调用函数 ‘func_c’,再修改Makefile,修改后的代码如下:
test: a.o b.o c.o
gcc -o test $^
%.o : %.c
gcc -c -o $@ $<
执行:
make
结果:
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -c -o c.o c.c
gcc -o test a.o b.o c.o
运行:
./test
结果:
This is B
This is C
基础语法之伪目标: .PHONY
what:什么是伪目标?
要搞清楚什么是伪目标,前提是明白目标是什么?Makefile中的目标究竟是什么?实际上,在默认情况下:
①make命令将Makefile的目标认为是一个文件;
②make解释器比较目标文件和依赖文件的新旧关系,决定是否执行命令;
③make以文件作为第一优先级。
如果不进行特殊的Makefile控制,make解释器在解析Makefile文件时,在解析到一个规则时,会将这个规则中的目标认为是一个文件,并进一步判断目标文件和依赖文件的新旧关系。
编写以下的makefile文件,并执行make clean。
正常情况下,当前目录下的*.o hello.out文件全部被删除了,没有任何错误,但当我们在当前目录下新建一个名字为clean的文件,然后再执行make clean,此时会提示clean文件是最新的。如图所示:
这是因为make解释器默认将clean目标当作一个文件处理,而不是一个标签。而将clean当作一个文件的时候,make发现当前目录下有此文件,而且此目标没有依赖,即认为目标是最新的。最终make给出了clean是最新的结论。
那么怎么解决这个问题呢?幸好,gun make中提供了关键字.PHONY,这个关键字用于定义一个伪目标,此时,伪目标不再对应任何实际的文件,仅仅只是作为一个标签,命令也总会执行。
伪目标实质:伪目标是make中特殊目标.PHONY的依赖。
why:为什么要使用伪目标?
当同一目录下出现了与命令同名的文件,就会导致命令无法执行,为了避免出现这个问题,从而使用伪目标来解决这个问题。此时make不再将伪目标当作文件处理,而是当成一个标签。不管伪目标的依赖是否更新,命令总是会被执行。
how:如何使用假想目标?
在Makfile结尾添加.PHONY: xxx语句即可,xxx为你想要的总是会被执行的命令。需要的时候,直接输入命令make xxx ,就可以执行此条命令了。
举例,还是接上文的程序,在Makefile文件中写下:
使用make命令运行
可以看到,正常使用。
基础语法之变量
what:有什么变量?
在Makefile中有两种变量:简单(即时)变量、延时变量。
①简单(即时)变量
变量的值即刻确定,在定义的时候就已经确定了。
②延时变量
变量要使用到的的时候才会确定,在定义等于时并不存在
常用的变量的定义如下:
:= # 即时变量
= # 延时变量
?= # 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略这句
\+= # 附加, 它是即时变量还是延时变量取决于前面的定义
?=: # 如果这个变量在前面已经被定义了,这句话就会不会起效果,
‘#’后面可以添加注释,不会对代码造成影响。
why:为什么要使用变量?
因为我们可以把变量的真实值推到后面来定义。
how:如何使用变量?
实例:
A := $(C)
B = $(C)
C = abc
#D = 100ask
D ?= weidongshan
all:
@echo A = $(A)
@echo B = $(B)
@echo D = $(D)
C += 123
代码符号解释:
‘@’ 符号的使用
通常makefile会将其执行的命令行在执行前输出到屏幕上。如果将‘@’添加到命令行前,这个命令将不被make回显出来。
例如:
@echo ABC;
// 屏幕输出 ABC
echo ABC
// 没有@ 屏幕输出echo ABC
$ '符号的使用
美元符号
,主要打开
M
a
k
e
f
i
l
e
中定义的变量。注:
m
a
k
e
定义了很多默认变量,
,主要打开Makefile中定义的变量。 注:make 定义了很多默认变量,
,主要打开Makefile中定义的变量。注:make定义了很多默认变量,(MAKE)就是预设的 make这个命令的名称(或者路径)
执行:
make
结果如图所示:
A =
B = abc 123
D = weidongshan
分析:
- A := $©:
A为即使变量,在定义时即确定,由于刚开始C的值为空,所以A的值也为空。
-
B = $©:
B为延时变量,只有使用到时它的值才确定,当执行make时,会解析Makefile里面的所用变量,所以先解析C= abc,然后解析C += 123,此时,C = abc 123,当执行:@echo B = $(B) B的值为 abc 123。 -
D ?= weidongshan:
D变量在前面没有定义,所以D的值为weidongshan,如果在前面添加D = 100ask,最后D的值为100ask。
我们还可以通过命令行存入变量的值 例如:
执行:make D=123456 里面的 D ?= weidongshan 这句话就不起作用了。
结果:
A =
B = abc 123
D = 123456
Makefile的函数
Why:为什么我们要使用函数?
在Makefile中可以使用它内部的函数来处理文本,从而让我们的命令或规则更加智能。函数调用之后,函数的返回值可以当作变量来使用。
What&&How:Makefile自带的函数有什么以及如何使用?
若想详细了解相关的内容,可查看官方文档:
https://www.gnu.org/software/make/manual/make.pdf
前奏:函数的调用语法
Makefile里面包含了一些函数,这些函数都是make本身实现的。
Makefile中调用一个函数就用’$‘符号。
$(<function><arguments>)
或者
${<function><arguments>}
函数调用以‘$‘开头,用‘()’(圆括号)或 ‘{}’(花括号)括起来,像是对一个变量的引用,函数中参数可以使用变量。其中, 为函数名,为函数参数。参数间用逗号‘ ,’分离,而函数名和参数间用空格分离。
字符串替换与分析函数
patsubst
函数 patsubst 语法如下:
$(patsubst <pattern>,<replacement>,<text>)
功能:查找
可以包括通配符%,表示任意长度的字符串。
中若包含%,那么这个%是中的那个%所代表的字符串
返回:函数返回被替换过后的字符串。
举例:
files2 = a.c b.c c.c d.c e.c abc
dep_files = $(patsubst %.c,%.d,$(files2))
all:
@echo dep_files = $(dep_files)
结果:
dep_files = a.d b.d c.d d.d e.d abc
findstring
函数 findstring 语法如下:
$(findstring <FIND>,<IN>)
功能:从字符串中查找指定的字符串,找到就返回,没找到返回空。
举例:
$(findstring a,b c)
$(findstring a,a b c)
结果:
a
第一个函数结果返回空,第二个函数结果为字符串‘a’。
filter filter-out
函数 filter 语法如下:
$(filter <pattern...>,<text>) # 在text中取出符合patten格式的值
功能:以模式过滤
函数filter-out 语法如下:
$(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)
结果:
D = d/
E = a b c
文件名称处理函数
wildcard
函数Wildcard语法如下:
$(wildcard <pattern...>)
功能:这个函数 wildcard 会以 这个格式,去寻找所有存在的文件,返回存在文件的名字,文件名以空格分隔。若不存在任何符合此格式的文件,则返回空。
实例:
在该目录下创建三个文件:a.c b.c c.c
files = $(wildcard *.c)
all:
@echo files = $(files)
结果:
files = a.c b.c c.c
会返回make工作目录下所有以 ’ .c ’ 为后缀的文件名。
make控制函数
info
函数info语法如下:
$(info <text>)
功能:这个函数会标准输出打印文本
实例:
$(info some debug info)
结果:
some debug info
warning
函数warning语法如下:
$(warning <text>)
功能:这个函数会向标准输出打印文本
实例:
$(warning some warning info)
结果:
some warning info
error
函数error语法如下:
$(error <text>)
功能:这个函数会向标准错误输出打印文本
实例:
ERROR="can't find commad g++"
ifdef ERROR
$(error error is $(ERROR1))
endif
结果:
makefile:3: *** error is "can't find commad g++". Stop.
解析:因为ERROR值非空,所以输出错误信息如下错误信息,并停止make的执行。
其他函数
foreach
函数foreach语法如下:
$(foreach <var>,<list>,<text>)
功能:把参数中的单词逐一取出放到参数所指定的变量中,然后再执行
所以,var是一个变量名,list是一个元素列表,而text中会使用var这个参数依次枚举list中的元素。
实例:
A = a b c
B = $(foreach f, $(A), $(f).o)
all:
@echo B = $(B)
结果:
B = a.o b.o c.o
代码解释:$(A)中的单词会被挨个取出,并且是存到变量 ‘ f ’ 中,‘ $(f).o ’ ,每次根据‘ ( f ) ’计算出一个值,这些值以空格分隔,最后作为 f o r e a c h 函数的返回,所以, (f) ’计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以, (f)’计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,(B)的值是 “a.o b.o c.o”。
注意:foreach中的参数是一个临时的局部变量,foreach函数执行完后,参数的变量将不在作用,其作用域只在foreach函数当中。
Makefile的实例
头文件依赖(生成*.d依赖文件及与-M相关参数介绍)
1. 为什么要使用自动依赖文件?
在Makefile中,目标文件的依赖关系需要包含一系列的头文件。
假设有如下程序:
c.c:
#include <stdio.h>
#include <c.h>
void func_c()
{
printf("This is C = %d\n", C);
}
c.h:
#define C 10086
那么有如下的依赖关系:
c.o:c.c stdio.h c.h
如果在使用Makefile的时,编写目标文件的依赖关系没有加上c.h文件,当c.h中的内容改变的时候,根本不会重新编译新的c.o文件,这会发生十分致命的错误,因为目标文件c.o内部引用了c.h文件中的宏定义。
若是大型的工程,我们必须清楚每个源文件中包含了哪些头文件,一旦增加或删除某些头文件,又要修改相应的Makefile,这就是很繁琐且很容易出错的工作。
因此,为了避免出现以上问题,我们需要做出改变,让它自动获取源文件中包含的头文件,并生成一个依赖关系,此时就使用后缀名为.d 的依赖文件。
这样做的优点就是:
- 不必手动书写若干目标文件的依赖关系,由编译器自动生成
- 不管是源文件还是头文件有更新,目标文件都会重新编译
2、让编译器自动生产依赖关系的相关参数
部分参数如下:
- -M
- -MF
- -MD
-M
生成文件的依赖关系,同时也把一些标准库的头文件包含了进来。其本质是告诉预处理器输出一个适合 make 的规则,用于描述各目标文件的依赖关系。
举例:
gcc -M c.c #打印出依赖
则在终端中就会输出:
这个参数同时可以查看某个源文件所包含的所有头文件。可以检查自己希望程序包含的头文件有没有包含进去。
-MF File
当同时使用了 “-M”选项时,则把依赖关系写入名为 “File” 的文件中。若同时也使用了 “-MD” 或 “-MMD”,“-MF” 将覆写输出的依赖文件的名称 。
举例:
gcc -M -MF c.d c.c #把依赖写入文件c.d
结果:
“-M”输出的内容就保存在c.d文件中。可使用“cat c.d”命令查看依赖。
-MD
等同于 -M -MF File,但是默认关闭了 -E 选项。其输出的文件名是基于 -o 选项,若给定了 -o 选项,则输出的文件名是 -o 指定的文件名,并添加 .d 后缀,若没有给定,则输入的文件名作为输出的文件名,并添加 .d 后缀,同时继续指定的编译工作。
举例:
gcc -c -o tmp.o -MD main.c
本目录下生成了以下文件:
tmp.d tmp.o
另一个例子:
gcc -c -o c.o c.c -MD -MF c.d #可以编译生成c.o, 并生成依赖写入文件c.d中
利用上文所说的参数,修改原本的Makefile。
修改Makefile如下:
objs = a.o b.o c.o
dep_files := $(patsubst %,.%.d, $(objs))
dep_files := $(wildcard $(dep_files))
test: $(objs)
gcc -o test $^
ifneq ($(dep_files),)
include $(dep_files)
endif
%.o : %.c
gcc -c -o $@ $< -MD -MF .$@.d
clean:
rm *.o test
distclean:
rm $(dep_files)
.PHONY: clean
首先用objs变量将.o文件放在一块。
利用前面讲到的函数,把objs里所有文件都变为.%.d格式,并用变量dep_files表示。
利用前面介绍的wildcard函数,判断dep_files是否存在。
然后是目标文件test依赖所有的.o文件。
如果dep_files变量不为空,就将其包含进来。
然后就是所有的.o文件都依赖.c文件,且通过-MD -MF生成.d依赖文件。
clean清理所有的.o文件和目标文件
distclean清理依赖.d文件。
现在不论修改了任何.h文件,最终都不会影响最后生成的文件,也没任何手工添加.h、.c、.o文件,完成了支持头文件依赖。
添加CFLAGS
What:什么是CFLAGS?
Makefile中有选项CFLAGS,LDFLAGS,LIBS。
CFLAGS 表示用于 C 编译器的选项。CXXFLAGS 表示用于 C++ 编译器的选项。
这两个变量实际上涵盖了编译和汇编两个步骤。
CFLAGS: 指定头文件(.h文件)的路径,如:CFLAGS=-I/usr/include -I/path/include。同样地,安装一个包时会在安装路径下建立一个include目录,当安装过程中出现问题时,试着把以前安装的包的include目录加入到该变量中来。
LDFLAGS:gcc 等编译器会用到的一些优化参数,也可以在里面指定库文件的位置。
用法:LDFLAGS=-L/usr/lib -L/path/to/your/lib。每安装一个包都几乎一定的会在安装目录里建立一个lib目录。如果明明安装了某个包,而安装另一个包时,它愣是说找不到,可以给那个包的lib路径加入的LDFALGS中试一下。
LIBS:告诉链接器要链接哪些库文件,如LIBS = -lpthread -liconv
简单地说,LDFLAGS是告诉链接器从哪里寻找库文件,而LIBS是告诉链接器要链接哪些库文件。不过使用时链接阶段这两个参数都会加上,所以你即使将这两个的值互换,也没有问题。
How:如何使用CFLAGS?
CFLAGS部分参数如下:
后 缀 名 | 所对应的语言 |
---|---|
-S | 只是编译不汇编,生成汇编代码 |
-E | 只进行预编译,不做其他处理 |
-g | 在可执行程序中包含标准调试信息 |
-o file | 把输出文件输出到file里 |
-v | 打印出编译器内部编译各过程的命令行信息和编译器的版本 |
-I dir | 在头文件的搜索路径列表中添加dir目录 |
-L dir | 在库文件的搜索路径列表中添加dir目录 |
-static | 链接静态库 |
-llibrary | 连接名为library的库文件 |
-I 参数使用的比较多。因为Linux下的大多数函数都默认:
-
头文件放到/usr/include/目录下
-
库文件则放到/usr/lib/目录下
GCC在编译时必须有自己的办法来查找所需要的头文件和库文件。
-I选项可以向GCC的头文件搜索路径中添加新的目录。
Gcc的告警和出错选项:
选 项 | 含 义 |
---|---|
-ansi | 支持符合ANSI标准的C程序 |
-pedantic | 允许发出ANSI C标准所列的全部警告信息 |
-pedantic-error | 允许发出ANSI C标准所列的全部错误信息 |
-w | 关闭所有告警 |
-Wall | 允许发出Gcc提供的所有有用的报警信息 |
-Werror | 把所有的告警信息转化为错误信息,并在告警发生时终止编译过程 |
建议:编译程序的时候,可以加上参数-Werror,因为很多警告都会隐藏错误。
一般来说,可以使用CFLAGS加上编译参数-Werror,把所有的警告当成错误。使用-Iinclude,指定include目录是编译器搜索的,默认的文件目录,此时,包含某个.h文件时,可以不用“”(双引号),可以使用<>(尖括号)。使用双引号时是当前目录下,使用尖括号时是去编译器下面所指定的路径查找头文件,当然也会去gcc默认目录下查找文件。
CFLAGS = -Werror -Iinclude
…………
%.o : %.c
gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d
现在重新make,发现以前的警告就变成了错误,必须要解决这些错误编译才能进行。可以在a.c
里面声明一下函数:
void func_b();
void func_c();
重新make,错误就没有了。
除了编译参数-Werror,还可以加上-I参数,指定头文件路径,-Iinclude表示当前的inclue文件夹下,一般来说include文件夹下放所有的.h文件。
此时就可以把c.c文件里的#include ".h"
改为#include <c.h>
,前者表示当前目录,后者表示编译器指定的路径和GCC路径。
参考资料
①百问网资料:http://download.100ask.net/#
②博主:天道酬勤: https://www.cnblogs.com/wanmeishenghuo/p/8409176.html
③博主:Dabelv:https://cloud.tencent.com/developer/article/1406069
④GUN make官方文档: https://www.gnu.org/software/make/manual/make.pdf
⑤博主:Jerry.yl : https://blog.csdn.net/qq1452008/article/details/50855810
⑥博主:mutes:http://blog.chinaunix.net/uid-20672257-id-3408132.html