Linux编程(3) MakeFile
1. 在Linux中,make工具可以维护程序模块关系和生成可执行程序。它可根据程序模块的修改情况重新编译链接生成中间代码或最终的可执行程序。执行make命令,需要一个名为Makefile的文本文件,其定义了模块间的依赖关系,指定文件的编译顺序,以及编译所使用的命令。make和Makefile文件使整个项目的源程序文件可以自动编译,极大地提高了软件开发效率。
一个简单的Makefile文件:
main:main.o module1.o module2.o gcc main.o module1.o module2.o -o main main.o: main.c head1.h head2.h common_head.h gcc -c main.c module1.o:module1.c head1.h gcc -c module1.c module2.o:module2.c head2.h gcc -c module2.c #This is a makefile
可以看出,Makefile文件的基本单元是规则,一条规则指定一个或多个目标文件,目标文件后面跟的是编译生成该目标文件所依赖的文件或模块,最后是生成或更新目标文件所使用的命令。
需要注意的是:如果某一行是命令,那么它必须以一个Tab键开头。在依赖文件列表后加上一个分号(;),可以跟上命令。如:
main:main.o module1.o module2.o ;gcc main.o module1.o module2.o -o main
main.o: main.c head1.h head2.h common_head.h
gcc -c main.c
(1) make的执行过程
假设上述Makefile文件及其所涉及到的源文件、头文件都在当前目录中,执行make命令就开始自动编译。
make首先在当前目录下寻找名为Makefile的文件,找到后,在当前目录下寻找第一行中 的目标文件main,发现没有,就去寻找生成main文件所依赖的文件即:main.o module1.o module2.o,发现也没有,然后跳过第二行的编译命令。定位到第2行,第3行中的目标文件main.o也没有,但它所依赖的源文件和头文件都在当前目录下找到,于是执行第4行命令,从而生成mian.o文件。之后make定位到第5行,发现目标文件module1.o没有,但它所依赖的文件都被找到,于是执行第6行的编译命令,生成module1.o,之后两行类似。make然后定位到最后一行,发现注释行,不予理睬。之后make回溯到第1行,此时依赖的文件都已生成,于是执行第二行的编译命令,最后生成了目标文件main。
执行make的输出如下:
gcc -c main.c gcc -c module1.c gcc -c module2.c gcc main.o module1.o module2.o -o main
(2) 假设修改了头文件head1.h,然后执行make,此时是如何工作的呢?
make首先在当前目录下寻找名为Makefile的文件,找到后,再在当前目录下寻找第一行的目标文件main,发现已存在,然后寻找main的依赖文件:main.o module1.o module2.o,发现也存在。make就开始比较目标文件main和它的3个依赖文件的修改时间,发现main的修改时间比它的3个依赖文件晚,于是make就认为目标文件main是最新的,那么就没必要执行第2行的命令以生成目标文件main了。
之后make定位到第3行,由于刚刚修改了头文件head1.h,导致head1.h的修改时间比main.o晚,于是make认为目标文件main.o是过时的,就执行第4行的命令从而生成了一个新的main.o文件。然后make定位到第5行,发现head1.h比module1.o新,就执行第6行的命令生成一个新的module1.o。make再定位到第7行,经过比较文件修改时间,判断出不用执行第8行的命令。
从头到尾扫描一遍Makefile文件后,make开始回溯,当回溯到第1行时,发现main.o module1.o的修改时间比目标文件main晚,于是就执行第2行的命令,从而生成一个新版本的main。
注意:1) 命令行之间可以插入任意多个空行,空行也要按Tab开头;2) 如果某一行过长,可以在达到这一行行末前输入一个反斜杠(\),由其连接起来的多行被当做一行来处理;3) makefile命名为其他文件时,应明确告诉make哪个是Makefile文件,如:make -f othername;4) 一般把最后要生成的文件放在第一条规则的目标文件列表中。
2. Makefile文件的构成
(1) 显示规则:指明了目标文件、所依赖的文件、生成或更新目标文件所用的命令;
1) Makefile中符号"$"有特殊含义,需要符号"$"的地方,需书写"$$"。
foo : foo.c | somelib
gcc -o foo foo.c somelib
“|”前面的文件是普通依赖文件,如果foo.c过时,将导致foo重新生成,而如果后面的文件过时,foo不会被重新生成。
2) 命令行属性
在Tab键后加上+、-、@,其意义如下:
- : 在执行本命令行的命令时如果远东啊错误,继续执行而不退出make;
+ : 本行命令始终被执行,即使运行make命令时使用了-n、-q、-t选项;
@ : 执行笨命令时不在屏幕上打印命令的内容
例:发现file.o过时,先将过时的file.o备份到/tmp下,然后再生成新的目标文件。
file.o : file.c hea1.h head2.h -mv file.o /tmp gcc -c file.c
且第一次执行时foo.o不存在,mv命令报错,但仍会继续执行("-"的作用)。
3) 伪目标:不要求生成实际文件,而是为了让make执行一些辅助命令,如打印信息,删除无用的中间文件等
clean:
-rm -f *.o
当make扫描到上面两行中的第1行,发现没有依赖文件,因此总认为是最新的,从而不去执行第2行的命令,可以在shell提示符后面使用命令"make clean"执行"clean:"后的语句。
当前目录若存在clean的文件,上述命令也不会执行。可以将"clean"目标声明为伪目标。方法是:将它作为特殊目标".PHONY"的依赖。书写伪目标的规则时,首先需要声明目标是一个伪目标,之后才是伪目标的规则定义。
.PHONY : clean
clean :
-rm -f *.o
这样无论在当前目录中是否存在"clean"这个文件输入"make clean"之后,"rm"命令都会被执行。
在一个目录下如果需要生成多个可执行程序,可使用一个称为"all"的伪目标作为最终目标,它的依赖文件是那些需要创建的可执行程序。
all : program1 program2 program3 .PHONY : all program1 : program1.c utils.h gcc -o program1 program1.c program2 : program2.c gcc -o program2 program2.c program3 : program3.c comm.h utils.h gcc -o program3 program3.c
4) 特殊目标
.PHONY:目标".PHONY"的所有依赖被作为伪目标,当使用make命令制定此目标时,这个目标所在规则中的命令无论目标文件是否存在都会被无条件执行。
.IGNORE:对于目标".IGNORE"后面跟的依赖文件,生成这些依赖文件的命令在执行时如果遇到错误,make将忽略错误继续执行。
.SUFFIXES:该目标的依赖被认为是一个后缀列表,在检查后缀规则时使用。
.SILENT:对于该目标的依赖文件,执行生成依赖文件的命令时,make不会打印出所执行的命令。如果后面没有依赖文件,则表示执行Makefile中的所有文件时都不会打印。
.PRECIOUS:如果make被kill命令终止或遇到意外而终止,目标的依赖文件不会被删除,而且如果这些依赖文件是中间文件,在不需要时也不会被删除。
.INTERMEDIATE:该目标的依赖文件在make执行时被当做中间文件对待。
5) 含有多个目标的规则
多目标规则意味着所有的目标具有相同的依赖文件。
module1.o module2.o module3.o : command.h #等价于 module1.o : module1.c gcc -c module1.c -o module1.o module2.o : module2.c gcc -c module2.c -o module2.o module3.o : module3.c gcc -c module3.c -o module3.o
6) 搜索目录
make通过变量"VPATH"可以执行依赖文件的搜索目录,"VPATH"变量指定的是Makefile中所有文件的搜索路径,包括规则的依赖文件和目标文件。
定义变量”VPATH“,使用空格或冒号(:)将多个需要搜索的目录分开。
VPATH = /usr/src; ../headers
这样就为所有规则指定了两个搜索目录。
也可以使用make的"vpath"关键字设置文件搜索目录,可为不同的文件指定不同的搜索目录。使用方法:
vpath <pattern> <directories> #为符合模式<pattern>的文件指定搜索目录<directories>
vpath <pattern> #清除符合模式<pattern>的文件的搜索目录
vpath #清除所有已设置好了的文件搜索目录
<pattern>中包含"%"字符,意思是匹配零或若干字符,如"%.h"匹配所有以".h"结尾的文件。
# make在”../headers“目录下搜索所有以".h"结尾的文件 vpath %.h ../headers
如果连续的vpath语句中出现了相同的<pattern>,make会按照vpath语句的先后执行顺序来执行搜索。
(2) 隐式规则:由make根据目标文件而自动推导出的规则。make根据目标文件的文件名,自动产生目标的依赖文件和生成目标的命令。如:
module1.o: head1.h #等价于如下规则
module1.o: module1.c head1.h
gcc -c module1.c -o module1.o
涉及C语言的隐含规则:
# 没有后缀的目标文件 program : header1.h header2.h #隐含规则 program : program.c header1.h header2.h gcc -o program program.c # make中可表示为(CC默认为cc,CFLAGS默认为-o,$@指代目标文件,$<指代第一个依赖文件): $(CC) $(CFLAGS) $@ $<
#以.o结尾的目标文件 program.o : header1.h header2.h #隐含规则 program.o : program.c header1.h header2.h gcc -c program.c -o program.o #在make中表示为 $(CC) -c $< $@
(3) 使用变量:可以使用一个字符串代表一个文本串。当定义了变量后,Makefile被make解释执行时,其中的字符串都会被替换为相应的文本串。
Makefile中定义变量的一般形式是:变量名 赋值符 变量值
1) 变量的引用方式:$(变量名)或${变量名}
2) 定义变量:两种类型变量:递归展开变量(通过"="赋值)和立即展开变量(通过":="赋值)
foo = $(bar) bar = ${ugh} ugh = Huh all: @echo $(foo)
#这种定义的好处是:在变量未定义时就可以使用该变量
# 缺点是:可能造成死循环,如:CFLAGS = $(CFLAGS)-o
x := foo y := $(x) bar x := later all: echo $(x) $(y)
# 在定义时立即展开,而不是在引用该变量时才展开。
CFLAGS := $(include_dirs) -O
include_dirs := -lfoo -lbar
# CFLAGS的值是"-O",而不是"-lfoo -lbar -O",因为CFLAGS是在定义时立即展开,
# 而此时的变量include_dirs还没定义,因此$(include_dirs)为空
条件赋值符"?=":只有此变量在之前没有赋值的情况下才会对这个变量进行赋值。
FOO ?= bar # 如果变量"FOO"在之前没有定义,就给它赋值”bar“,否则不改变它的值
追加赋值操作符"+="实现对一个变量值的追加操作。
objects - main.o foo.o bar.o utils.o objects += another.o # 将字符串"another.o"添加到变量"objects"原有值的末尾,并用空格与原有值分开。
3) 常用的预定义变量
宏名 | 初始值 | 说明 |
CC | cc | 默认使用的编译器 |
CFLAGS | -o | 编译器使用的选项 |
MAKE | make | make命令 |
MAKEFLAGS | 空 | make命令的选项 |
SHELL | 默认使用的Shell类型 | |
PWD | 运行make命令时的当前目录 | |
AR | ar | 库管理命令 |
ARFLAGS | -ruv | 库管理命令选项 |
LIBSUFFIX | -a | 库的后缀 |
A | a | 库的扩展名 |
# 原有规则 module1.o : module1.c head1.h gcc -c module1.c # 改变为如下规则,CC是系统预定义的变量,可直接使用 module1.o : module1.c head1.h $(CC) -c module1.c # 可改变预定义变量的值 module1.o : module1.c head1.h $(CC) = gcc $(CC) -c module1.c
4) Makefile的自动变量,值在make运行过程中可以动态改变
$@:表示一个规则中的目标文件名,如果目标是一个文档文件,则它代表这个文件名。
file1.o file2.o : header.h cp $@ /backup # 功能:当目标文件过时时,将原来的目标文件备份到/backup目录下,然后重新生 # 成新的目标文件,当需要更新的时file1.o(file1.c比file1.o新),$@就等于file1.o,file2.o类似 # 上述隐含规则等价于如下 file1.o : file1.c header.h cp $@ /backup gcc -c file1.c -o file1.o file2.o : file2.c header.h cp $@ /backup gcc -c file2.c -o file2.o
$%:当规则目标文件是一个静态库文件时,$%代表静态库的一个成员名。如果目标不是静态库文件,$%值为空
#规则目标是foo.a(bar.o),则$%的值为bar.o,$@的值为foo.a
$<:规则中的第一个依赖文件名,若使用了隐含规则,则$<的值是由隐含规则引入的第一个依赖文件名。
file1.o file2.o : header.h cp $@ /backup #当file1.o过时时,该规则相当于 file1.o : file1.c header.h cp $@ /backup gcc -c file1.c -o file1.o #因此规则中的第一个依赖文件名,即$<的值为file1.c,而非header.h。
$>:只适用于静态库文件,值为库名。如foo.a(bar.o),则$%为bar.o,$@值为foo.a,$>值也为foo.a。非静态库文件则为空。
$?:所有比目标文件新的依赖文件列表,以空格分隔。如果目标是静态库文件名,代表的是库成员。
$^:规则中所有依赖文件列表,使用空格分隔,且会去掉重复的依赖文件。
$+:类似$^,但保留了依赖文件中重复出现的文件,主要用在程序链接时库的交叉引用场合
$*:它的值是目标文件去掉后缀后的名称。如:目标文件module1.o,则$*值为module1。
file1.o file2.o : header.h gcc -c $*.c -o $@
注:以上自动变量只能用在规则的命令中,若想在目标文件列表或依赖文件列表中使用,要在它们之前加上一个$,如$$*
file1.o file2.o : $$*.c header.h gcc -c $*.c -o $@ # 当处理的目标文件为file1.o,$$*和$*代表file1,当处理目标文件file2.o,则为file2
(4) 文件指示:包含:1) 在一个Makefile中包含另一个Makefile(相当于include);2) 根据某些情况指定Makefile中的有效部分(相当于预编译#if);3) 定义一个多行的命令。
例:使用条件语句对变量"CC"进行判断,其值如果是"gcc",那么在程序链接时使用库"libgnu",否则不链接任何库。
libs_for_gcc = -lgnu normal_libs = ifeq ($(CC),gcc) libs=$(libs_for_gcc) else libs=$(normal_libs) endif foo : foo.c $(CC) -o foo foo.c $(libs)
(5) 注释:Makefile中#字符后的内容被当做注释处理,如需使用字符#时,可以用反斜线加#(\#)来表示。
3. 使用库
链接生成可执行文件时,如果链接的时一般的.o文件,是把整个.o文件的内容插入到文件中,而如果链接的是库,则只从库中找出程序需要的变量和函数,把它们装入到可执行文件中。
库中的文件一般称为库的成员,表示形式为:库名(成员名)
mylib.a(file.o) #表示静态库myli.a(动态库以.so结尾)中有一个名为file.o的文件。
静态库通常使用ar命令对它进行维护和管理。建立库时将库名作为目标文件,把希望放到库中的文件作为依赖文件,的格式为:
库名: 库名(成员1) 库名(成员2)... 或者 库名: 库名(成员1,成员2...)
接着,输入命令:ar -ruv 库名 目标文件名
例:将file1.o、file2.o、file3.o三个文件加入到mylib库中
mylib:mylib(file1.o) gcc -c file1.c ar -ruv mylib file1.o rm -f file1.o mylib:mylib(file2.o) gcc -c file2.c ar -ruv mylib file2.o rm -f file2.o ...
使用自动变量可以将上面的规则简化:
mylib:mylib(file1.o file2.o file3.o) ar -ruv $@ $? rm -f $? file1.o:file1.c gcc -c file1.c file2.o:file2.c gcc -c file2.c file3.o:file3.c gcc -c file3.c
进一步利用隐含规则简化:
mylib:mylib(file1.o file2.o file3.o) ar -ruv $@ $? rm -f $?
make也建立了相应的针对库操作的隐含规则,类似于:
$(AR) $(ARFLAGS) $@ $? rm -f $? # AR、ARFLAGS为make的预定义变量,初始值分别为ar、-ruv
因此上面的规则进一步简化:
mylib:mylib(file1.o file2.o file3.o)
4. make命令参数详解
-C dir | 读取MakeFIle之前,先切换到dir目录下,并将其作为当前目录 |
-d | make执行时打印出所有的调试信息 |
-e | 不允许在Makefile中对系统环境变量进行重新赋值 |
-f filename | 使用指定文件作为Makefile文件 |
-i | 忽略执行Makefile中命令时产生的错误,不退出make |
-h | 打印出帮助信息 |
-k | 执行命令遇到错误时不终止make的执行 |
-n | 只打印出要执行的命令,但不执行命令 |
-o filename | 指定filename文件不需要重建,即使相对于它的依赖已经过时 |
-p | 命令执行前打印出make读取的Makefile的所有数据,同时打印出make的版本信息 |
-q | 询问模式,不执行任何命令,make返回一个查询状态值,0表示没有目标需要重建,1表示存在需要重建的目标,2表示有错误发生 |
-r | 忽略隐含规则,使之不起作用,但不会取消make内嵌的预定义变量 |
-R | 取消make的内嵌的预定义变量,隐含规则野失去意义 |
-s | 执行但不显示所执行的命令 |
-t | 把所有目标文件的最后修改时间设置为当前系统时间 |
-v | 打印出make的版本信息 |
1. 不能把共用体变量作为函数参数,也不能使函数返回共用体变量,但可以使用指向共用体变量的指针。
2. C语言允许在一个结构体中以位为单位来使用内存,称为位域。
下面的结构体要求c后的位段要从另外一个存储单元开始:
struct bit_data{ int a: 6; int b: 4; int : 0; int c: 4; int d; };
下面的结构体要求10~17位的8位强制空闲:
struct bit_data { int a: 6; int b: 4; int : 8; int c: 4; int d: };