Makefile入门(介绍、规则、语法、函数、实例)

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.oyyy.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

分析:

  1. A := $©:

A为即使变量,在定义时即确定,由于刚开始C的值为空,所以A的值也为空。

  1. B = $©:
    B为延时变量,只有使用到时它的值才确定,当执行make时,会解析Makefile里面的所用变量,所以先解析C= abc,然后解析C += 123,此时,C = abc 123,当执行:@echo B = $(B) B的值为 abc 123。

  2. 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>) 

功能:这个函数会向标准输出打印文本 ,用于输出警告信息。make继续执行。

实例:

$(warning some warning info)

结果:

some warning info

error

函数error语法如下:

$(error <text>) 

功能:这个函数会向标准错误输出打印文本 ,用于输出指明错误信息。make停止执行。

实例:

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>) 

功能:把参数中的单词逐一取出放到参数所指定的变量中,然后再执行 所包含的表达式。每一次 会返回一个字符串,循环过程中, 的所返回的每个字符串会以空格分隔,最后当整个循环结束时,text所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。

所以,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

猜你喜欢

转载自blog.csdn.net/XieHYBlog/article/details/127034105
今日推荐