Makefile编写手册
Makefile 基础
Makefile 是一种用于管理和自动化软件编译过程的文本文件。它通常包含了一系列规则,这些规则描述了如何根据源代码文件生成可执行文件或者其他目标文件。Makefile的核心概念是规则和依赖关系,规则定义了如何生成一个或多个目标文件,而依赖关系则指定了生成目标文件所需要的源文件或其他依赖文件。
为main.c和hello.c编写基本Makefile
文件内容如下:
# Makefile内容通常由以下部分组成
# <目标>: <前置依赖>
# <需要执行的命令>
#放在第一个的是默认目标
#目标为编译出main文件,依赖main.o和hello.o文件
#编译的命令为gcc-o main hello.o main.o
main: hello.o main.o
gcc -o main hello.o main.o
# main.o目标依赖main.c hello.h
#编译命令为gcc-c main.c
main.o: main.c hello.h
gcc -c main.c
# hello.hello.c hello.h
#编译命令为gcc-c hello.c
hello.o: hello.c hello.h
gcc -c hello.c
# clean目标可以清理编译的临时文件
clean:
rm main main.o hello.o
需要注意的是,Makefile中每个规则的命令必须以一个制表符(tab)开始,而不能是空格。否则会提示“缺失分隔符”
上文提到,gcc的-c参数不仅可以将汇编代码转换为机器码,还可以直接将C语言源文件转换为机器码,gcc-c main.c就是第二种用法,这里省略了-o main.o。默认情况下,在指定-c参数时,gcc会将与源文件名去掉扩展名再加上后缀.o作为目标文件的名称。
引入变量
Makefile 中为了方便,可以引入临时变量:
# 定义变量objects
objects := hello.o\
main.o
# 在目标中引入变量
main: $(objects)
gcc -o main $(objects)
main.o: main.c hello.h
gcc -c main.c
hello.o: hello.c hello.h
gcc -c hello.c
# clean 目标中也可以引入变量
clean:
rm main $(objects)
引入make自动推导:
make 可以根据目标自动加入所需的依赖文件和命令。例如main.o目标,会默认将main.c 作为依赖加入,同时也可以自动推导出编译main.o的命令,于是我们的Makefile 就可以改成以下内容:
objects := hello.o\
main.o
main: $(objects)
gcc -o main $(objects)
# 利用make的自动推导
clean:
rm main $(objects)
要注意的是,虽然这种方式精简Makefile的内容,但是当没有显式声明的依赖文件发生更改时Make无法追踪。
例如此时Makefile工具无法检测到hello.h的更新。
将Makefile恢复为以下内容。
#Makefile内容通常由以下3部分组成
#<目标名称>:<前置依赖>
#\t<需要执行的命令>
#定义变量objects
objects:=hello.o\
main.o
main.o:hello.h
hello.o:hello.h
clean:
rm main $(objects)
引入伪目标
伪目标并不代表实际的文件名,它们更多的是行为或动作的标识符。伪目标并不生成具体文件。
.PHONY目标
① .PHONY 是Makefile 中一个特殊的目标,用于声明其它目标是伪目标。
② 语法:.PHONY:<伪目标名称>
③ 目标为clean的规则没有前置依赖,这是因为它是用来执行清理操作的,并不是要生成名为clean的文件,因此不需要前置依赖。我们可以将clean 声明为伪目标。
将某些不生成目标文件的行为或动作(如清理、安装)声明为伪目标可以确保无条件执行规则下的命令。即便执行make命令时当前目录下存在与目标同名的文件,依然可以得到我们期望的效果。
例如如果目录下有与clean同名的文件存在,我们就需要先声明伪目标clean命令才可以正确执行
.PHONY: clean
注意事项
忽略错误
clean:
-rm main $(objects)
在rm命令前加-即可忽略错误。 rm前面的-告诉make,如果该命令执行失败,不要停止执行剩余的过程,即忽略错误。
目标名和命令中输出文件名的关系
make 输出的文件名取决于规则下的命令(即gcc语句最后-o的文件名),而目标名称(第一条语句冒号前的文件名)决定make追踪的目标文件名。如果二者不一致,make就会认为目标文件不存在而不断执行命令。我们应确保命令生成的目标文件名和目标名一致。
Makefile 进阶知识
自动化变量
例如下面这段Makefile:
CC:=gcc
fopen_test: fopen_test.c
-$(CC)-o $@ $^
-./$@
-rm ./$@
①有时编译器不只是gcc,我们将编译器定义为变量CC,当切换编译器时只需要更
改该变量的定义,而无须更改整个Makefile。
②$@相当于当前target目标文件的名称,此处为fopen_test。
③$^相当于当前target所有依赖文件列表,此处为fopen_test.c
④./$@的作用是执行目标文件
⑤rm ./$@的作用是在执行完毕后删除目标文件,如果没有这个操作,当源文件fopen_test.c未更改时就无法重复执行,会提示:make:“fopen_test”已是最新。
此处删除目标文件,使得我们在不更改源文件的情况下可以多次执行。
⑥所有命令前都添加了“-”符号以忽略错误,确保即便上面的命令执行失败,仍然会向下执行。这样做是为了在发生错误时,确保删除目标文件,使得再次执行相同target时不会提示:make:“fopen_test”已是最新,可以重新执行target下的命令。
常见的自动化变量如下:
标志
在编译和链接过程中,标志(Flags)是传递给编译器和链接器的参数,它们指示编译器和链接器如何处理源代码和目标代码。
编译标志(CompilerFlags)
编译标志用于告诉编译器如何处理源代码。这些标志包括头文件的搜索路径、预处理器宏定义等。-I/usr/include/glib-2.0就是一个编译标志,-I是gcc编译时的一个参数,用于指定头文件的搜索路径。
链接标志(LinkerFlags)
链接标志用于告诉链接器如何处理目标文件和库文件。这些标志包括库文件的搜索路径和库文件名等。-lglib-2.0是一个链接标志,-l指定了要链接的库。-l选项后面跟库的名称,这里是glib-2.0。根据约定,链接器会搜索名为lglib-2.0.so(动态库)或lglib-2.0.a(静态库)的文件来链接。这个标志表示链接libglib-2.0.so 库。
pkg-config
pkg-config 是一个用于从已安装库中检索编译器和链接器标志的工具。它帮助开发者轻松地获取用于编译和链接程序所需的库信息。
选项有下面两个:
–cflags
打印编译包时所需的预处理器和编译器标志。示例如下
sudo apt-get update
sudo apt-get install libglib2.0-dev
atguigu@ubuntu:~/thread_test$ pkg-config --cflags glib-2.0
-I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include
返回了预处理器需要的标志,用于指明额外的头文件搜索路径。
–libs
用于打印链接器所需的标志。示例如下
atguigu@ubuntu:~/thread_test$ pkg-config --libs glib-2.0
-lglib-2.0
返回了链接器需要的标志,用于指明所需的动态链接库。表示链接libglib-2.0.so库。
pkg-config–cflags–libs glib-2.0 实际上是 pkg-config–cflags glib-2.0 和 pkg-config–libs glib-2.0 整合,二者是等价的,如下
atguigu@ubuntu:~/thread_test$ pkg-config --cflags --libs glib-2.0
-I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -lglib-2.0
Shell中的飘号或反引号(`)
'cmd`的作用是执行命令(cmd),将命令的标准输出作为变量或其他命令的参数。
即先执行反引号中的命令,将执行结果作为其它命令的参数,如下面将pkc-congif执行的需要路径变为编译中的路径参数。
-$(CC)-o $@ $^ `pkg-config--cflags--libs glib-2.0`
等价于
-$(CC)-o $@ $^-I/usr/include/glib-2.0-I/usr/lib/x86_64-linux-gnu/glib-2.0/include-lglib-2.0
实际上是通过pkg-config命令帮助我们指明了编译Glib所需的额外的头文件搜索路径和需要链接动态链接库。