Linux:Makefile
编译器gcc
使用方式:gcc [ 选项 ] 要编译的⽂件 [ 选项 ] [ ⽬标⽂件 ]
编译分为以下几个步骤:
1.预处理(进⾏宏替换)
预处理功能主要包括宏定义,⽂件包含,条件编译,去注释等。
预处理指令是以#号开头的代码⾏。
实例:
gcc –E hello.c –o hello.i
选项“-E”,该选项的作⽤是让gcc在预处理结束后停⽌编译过程。
选项“-o”是指⽬标⽂件,“.i”⽂件为已经过预处理的C原始程序。
2.编译(⽣成汇编)
在这个阶段中,gcc⾸先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的⼯作, 在检查⽆误后,gcc把代码翻译成汇编语⾔。
⽤⼾可以使⽤“-S”选项来进⾏查看,该选项只进⾏编译⽽不进⾏汇编,⽣成汇编代码。
实例:
gcc –S hello.i –o hello.s
3.汇编(⽣成机器可识别代码)
汇编阶段是把编译阶段⽣成的“.s”⽂件转成⽬标⽂件
读者在此可使⽤选项“-c”就可看到汇编代码已转化为“.o”的⼆进制⽬标代码了
实例:
gcc –c hello.s –o hello.o
4.连接(⽣成可执⾏⽂件或库⽂件)
在成功编译之后,就进⼊了链接阶段。
实例:
gcc hello.o –o hello
动态链接和静态链接
在实际开发中,不可能将所有代码放在⼀个源⽂件中,所以会出现多个源⽂件,⽽且多个源⽂件之间不是独⽴的,⽽会存在多种依赖关系,如⼀个源⽂件可能要调⽤另⼀个源⽂件中定义的函数, 但是每个源⽂件都是独⽴编译的,即每个*.c⽂件会形成⼀个*.o⽂件,为了实现这种依赖关系,则需要将这些源⽂件产⽣的⽬标⽂件进⾏链接,从⽽形成⼀个可以执⾏的程序。这个链接的过程就是静态链接。
静态链接的缺点很明显:
浪费空间:因为每个可执⾏程序中对所有需要的⽬标⽂件都要有⼀份副本,所以如果多个程序对 同⼀个⽬标⽂件都有依赖,如多个程序中都调⽤了printf()函数,则这多个程序中都含有 printf.o,所以同⼀个⽬标⽂件都在内存存在多个副本;
更新⽐较困难:因为每当库函数的代码修改了,这个时候就需要重新进⾏编译链接形成可执⾏程 序。
但是静态链接的优点就是,在可执⾏程序中已经具备了所有执⾏程序所需要的任何东西,在执⾏的时候运⾏速度快。
而动态链接则是把程序按照模块拆分成各个相对独⽴的部分,在程序运⾏时才将它们链接在⼀起形成⼀个完整的程序,⽽不是像静态链接⼀样把所有程序模块都链接成⼀个单独的可执⾏⽂件。
静态库和动态库
静态库是指编译链接时,把库⽂件的代码全部加⼊到可执⾏⽂件中,因此⽣成的⽂件⽐较⼤,但在运⾏时也就不再需要库⽂件了。
其后缀名⼀般为“.a”
动态库与之相反,在编译链接时并没有把库⽂件的代码加⼊到可执⾏⽂件中,⽽是在程序执⾏时由运⾏时链接⽂件加载库,这样可以节省系统的开销。
动态库⼀般后缀名为“.so”。
gcc在编译时默认使⽤动态库。完成了链接之后,gcc就可以⽣成可执⾏⽂件,
如:
gcc hello.o –o hello
gcc默认⽣成的⼆进制程序,是动态链接的。
make/Makefile
对于一个内容复杂、源文件众多的工程而言,仅使用gcc指令对其文件进行逐个编译会显得过于繁琐低效,因此linux提供了一个对工程进行自动编译的指令:make
make实际执行的是一个名为makefile的文件内的指令(该文件由用户自己创建)
因此,我们要想编译一个工程,不妨写一个makefile文件,这样每次编译时只需调用make指令即可
写makefile的思路:
形成目标文件的依赖关系(即由什么文件生成)+依赖方法(即如何生成)
以一个简单的makefile文件为例:
code:code.c
gcc -o code code.c
.PHONY:clean
clean:
rm -f code
依赖关系:形成code⽂件依赖于code.c
依赖⽅法:gcc -o myproc myproc.c
项⽬清理 :删除所有的目标文件
make是如何⼯作的
1. make会在当前⽬录下寻找名为“Makefile”或“makefile”的⽂件。
2. 如果找到,它会找⽂件中的第⼀个⽬标⽂件(target),在上⾯的例⼦中,他会找到code⽂件,并把这个⽂件作为最终的⽬标⽂件。
3. 如果 code ⽂件不存在,或是依赖文件code.c与目标文件code相比更新(即文件修改时间更晚),那么,他就会执⾏后⾯所定义的命令来⽣成 myproc 这个⽂件。
4. 如果 code 所依赖的 code.c ⽂件不存在,那么 make 会在当前⽂件中找⽬标文件为 code.c的依赖文件,如果找到则根据上述规则形成code.c⽂件(这里是一个递归调用,即不断寻找依赖文件,当找到最后一个依赖文件时,开始依次执行依赖方法)。
5. 在找寻的过程中,如果出现错误,⽐如最后被依赖的⽂件找不到,那么make就会直接退出,并 报错,⽽对于所定义的命令的错误,或是编译不成功,make不做处理。
clean
clean也是一个目标文件,但它不依赖于任何文件,也不与其他的目标文件直接或间接关联,这导致它后⾯所定义的命令将不会被⾃动执⾏,不过,我们仍然可以使用指令“make clean”来显式执⾏,以此来清除所有的⽬标⽂件,以便重编译。
而.PHONY则是将clean修饰为伪目标,效果是:让make忽略源⽂件和可执⾏⽬标⽂件的M时间对⽐(直白的讲就是clean可以总是被执行)
什么是总是被执行
当我们查看一个文件的信息注意到:
Access :⽂件最后⼀次被访问的时间。
Modify: 文件内容最后一次变更的时间
Change :文件属性最后一次变更的时间
当目标文件的修改时间晚于依赖文件时,此时编译器会认为目标文件已由依赖文件形成,不需再执行,而总是被执行就是无论目标文件的修改实际是否晚于依赖文件,命令都会被执行
Makefile的扩展语法
上面我们根据code.c文件编写了一个makefile,对于一个有着众多源文件的工程,像上面一样写则过于繁琐,而且通用性太差,每次编译一个新的工程都要重写一个makefile
因此,我们期望写一个通用的makefile模板,每次编译工程都可以使用这个模板:
BIN=code #最终目标文件
CC=gcc
#SRC=$(shell ls *.c) #采⽤shell命令⾏⽅式,获取当前所有.c⽂件名
SRC=$(wildcard *.c) #或者使⽤wildcard 函数,获取当前所有.c⽂件名
OBJ=$(SRC:.c=.o) #将SRC的所有同名.c 替换成为.o 形成⽬标⽂件列表
LFLAGS=-o #链接选项
FLAGS=-c #编译选项
RM=rm -f #引⼊命令 $(BIN):$(OBJ) @$(CC) $(LFLAGS) $@ $^ # $@:代表⽬标⽂件名。$^: 代表依赖⽂件列表@echo "linking ... $^ to $@"
%.o:%.c # %.c: 展开当前⽬录下所有的.c。%.o: 同时展开同名.o@$(CC) $(FLAGS) $< # %<: 对展开的依赖.c⽂件,⼀个⼀个的交给gcc。
@echo "compling ... $< to $@" # @:不回显命令
.PHONY:clean
clean:$(RM) $(OBJ) $(BIN) # $(RM): 替换,⽤变量内容替换它.PHONY:test
test: @echo $(SRC) @echo $(OBJ)