GCC和Makefile编译过程
gcc的使⽤⽅法
gcc [选项] ⽂件名
⼀个c/c++⽂件要经过预处理、编译、汇编和链接才能变成可执⾏⽂件。
(1)预处理
C/C++源⽂件中,以#开头的命令被称为预处理命令,如"#include"、宏定义命令"#define"、条件编译命令"#if、#ifdef"等。预处理是将包含(include)的⽂件插⼊原⽂件中、将宏定义展开、根据条件编译命令选择要使⽤的代码,最后将这些东西输出到⼀个.i⽂件中并等待进⼀步处理。
(2)编译
编译就是把C/C++代码(⽐如上述的.i⽂件)翻译成汇编代码。
(3)汇编
汇编就是将第⼆步输出的汇编代码翻译成符合⼀定格式的机器代码,在Linux系统上⼀般表现为ELF⽬标⽂件(OBJ⽂件)。反汇编是指将机器代码转换为汇编代码,这在调试程序时常常⽤到。
(4)链接
链接就是将上步⽣成的OBJ⽂件和系统库的OBJ⽂件、库⽂件链接起来,最终⽣成可以在特定平台运⾏的可执⾏⽂件。
hello.c(预处理) -> hello.i(编译) -> hello.s(汇编) -> hello.o(链接) -> hello
详细的每⼀步命令如下:
上⾯⼀连串命令有点多,gcc会对.c⽂件默认进⾏预处理操作,使⽤-c指明编译、汇编,从⽽得到.o⽂件, 再将.o⽂件进⾏链接,得到可执⾏应⽤程序。
简化的命令如下:
Makefile的引⼊及规则
Makefile要做什么事情呢? 组织管理程序和⽂件
⽂件a.c:
#include <stdio.h>
int main()
{
func_b();
return 0;
}
⽂件b.c:
#include <stdio.h>
void func_b()
{
printf("This is B\n");
}
编译:
gcc -o test a.c b.c
运⾏:
./test
结果:
This is B
gcc -o test a.c b.c这条命令虽然简单,但是它完成的功能不简单。
来看看它做了哪些事情。
我们知道.c程序 –> 得到可执⾏程序
要经过四个步骤:
1.预处理
2.编译
3.汇编
4.链接
我们经常把前三个步骤统称为编译。具体分析这条命令:gcc -o test a.c b.c
它们要经过下⾯⼏个步骤:
1).对于a.c执⾏:预处理 编译 汇编 的过程,a.c –>xxx.s –>xxx.o ⽂件。
2).对于b.c执⾏:预处理 编译 汇编 的过程,b.c –>yyy.s –>yyy.o ⽂件。
3).最后:xxx.o和yyy.o链接在⼀起得到⼀个test应⽤程序。
提⽰:gcc -o test a.c b.c -v :加上⼀个‘-v’选项可以看到它们的处理过程
第⼀次编译a.c得到xxx.o⽂件,这合乎情理, 执⾏完第⼀次之后,如果修改a.c ,⼜再次执⾏:gcc -o test a.c b.c,对于a.c应该重新⽣成xxx.o,但是对于b.c,⼜会重新编译⼀次,这完全没必要:b.c根本没有修改,直接使⽤第⼀次⽣成的yyy.o⽂件即可。
以上,不⽤Makefile的缺点:对所有的⽂件都会再处理⼀次,即使b.c没有经过修改,b.c也会被重新编译⼀次, 当⽂件⽐较少时,没什么问题,⽂件⾮常多的时候,编译的效率会很低。
如果⽂件⾮常多的时候,只是修改了⼀个⽂件,所有⽂件都会被重新编译⼀次,编译的时候就会需要很长时间。
对于这些源⽂件,我们应该分别处理,执⾏:预处理 编译 汇编 ,先分别编译它们,最后再把它们链接在⼀起⽐如:
编译:
linux下gcc编译的四个步骤gcc -o a.o a.c
gcc -o b.o b.c
链接:
gcc -o test a.o b.o
⽐如:上⾯的例⼦,当我们修改a.c之后,a.c会重新编译, 然后再把它们链接在⼀起就可以,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 就是这样做的。
现在写出⼀个简单的Makefile:
makefie最基本的语法是规则,规则:
⽬标 : 依赖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
来做⼀个实验:
在该⽬录下写⼀个Makefile⽂件:
⽂件:Makefile
test:a.o b.o
gcc -o test a.o b.o
a.o : a.c
gcc -c -o a.o a.c
b.o : b.c
gcc -c -o b.o b.c
上⾯是makefile中的三条规则。makefile就是名字为“makefile”的⽂件。当我们编译程序时,直接执⾏m
ake命令即可,⼀执⾏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的处理关系也是这样的。
如果修改a.c ,再次执⾏make,它的本意是想⽣成第⼀个⽬标test应⽤程序, 它需要先⽣成a.o, 发现a.o依赖a.c(假设我们修改了a.c),发现a.c⽐a.o更加新,就会执⾏gcc -c -o a.o a.c命令⽣成a.o⽂件。b.o依赖b.c,发现b.c并没有被修改,则不会执⾏gcc -c -o b.o b.c来重新⽣成b.o⽂件。
现在a.o b.o都有了,其中的a.o⽐test更加新,就会执⾏gcc -o test a.o b.o重新链接得到test可执⾏程序。所以当执⾏make命令,则会执⾏下⾯两条命令:
gcc -c -o a.o a.c
gcc -o test a.o b.o
第⼀次执⾏make的时候,会执⾏下⾯三条命令(三条命令都执⾏):
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -o test a.o b.o
再次执⾏make 会有下⾯的提⽰:
make: test is up to date.
再次执⾏make 会判断Makefile⽂件中的依赖,发现依赖没有更新,所以⽬标⽂件就不会重新⽣成,于是有上⾯的提⽰。当我们修改a.c 后,重新执⾏make会执⾏下⾯两条指令:
gcc -c -o a.o a.c
gcc -o test a.o b.o
同时修改a.c b.c,执⾏make则会执⾏下⾯三条指令。
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -o test a.o b.o
a.c⽂件被修改了,重新编译⽣成a.o,
b.c被修改了,重新编译⽣成b.o,
a.o,
b.o都更新了,则重新链接⽣成test可执⾏程序,makefile的规则其实不难
规则是Makefie的核⼼,执⾏make命令的时候,会在当前⽬录下到名为:Makefile的⽂件,根据⾥⾯的内容来执⾏⾥⾯的判断/命令。Makefile的语法
假如⼀个⽬标⽂件所依赖的依赖⽂件很多,我们岂不是要写很多规则?这显然不合乎常理。
我们可以使⽤通配符解决这些问题。我们对上节程序进⾏修改代码如下:
test: a.o b.o
gcc -o test $^
%.o : %.c
gcc -c -o $@ $<
%.o:表⽰所⽤的.o⽂件
%.c:表⽰所有的.c⽂件
$@:表⽰⽬标
$<:表⽰第1个依赖⽂件
$^:表⽰所有依赖⽂件
在该⽬录下增加⼀个c.c⽂件,代码如下:
#include<stdio.h>
void func_c()
{
printf("This is C\n");
}
在main函数中调⽤修改的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
1.我们若清除⽂件,在Makefile的结尾添加如下代码即可:
clean:
rm *.o test
1).执⾏make:⽣成第⼀个可执⾏⽂件。
2).执⾏make clean: 清除所有⽂件,即执⾏:rm *.o test。
2.使⽤Makefile
执⾏:make [⽬标]
也可以不跟⽬标名,若⽆⽬标默认第⼀个⽬标。执⾏make的时候,会在makefile⾥⾯到第⼀个⽬标然后执⾏下⾯的指令⽣成第⼀个⽬标。当执⾏make clean的时候,会在Makefile⾥⾯到clean这个⽬标,然后执⾏⾥⾯的命令,这个写法有些问题,原因是我们的⽬录⾥⾯没有clean这个⽂件,这个规则执⾏的条件成⽴,它就会执⾏下⾯的命令来删除⽂件。
如果:该⽬录下⾯有名为clean⽂件怎么办呢?
我们在该⽬录下创建⼀个名为“clean”的⽂件,然后重新执⾏make,make clean,结果(会有下⾯的提⽰:):
make: `clean’ is upto date.
它根本没有执⾏我们的删除操作,为什么?
我们之前说,⼀个规则能够执⾏的条件:
1).⽬标⽂件不存在
2).依赖⽂件⽐⽬标新。
现在我们的⽬录⾥⾯有名为“clean”的⽂件,⽬标⽂件是有的,并且没有依赖⽂件,没有办法判断依赖⽂件的时间。这种写法会导致:有同名的”clean”⽂件时,没有办法执⾏make clean操作。
解决办法:把⽬标定义为假想⽬标,⽤关键字PHONY。
.PHONY: clean //把clean定义为假想⽬标。它便不会判断名为“clean”的⽂件是否存在, 然后在Makfile结尾添加.PHONY: clean语句,重新执⾏:make clean,就会执⾏删除操作。
假想⽬标: 变量
在makefile中有两种变量:
· 1)简单变量(即时变量):
A := xxx // A的值即刻确定,在定义时即确定
对于即时变量使⽤“:=”表⽰,它的值在定义的时候已被确定。
· 2)延时变量
B = xxx // B的值被使⽤到时才确定
对于延时变量使⽤“=”表⽰。它只有在使⽤到的时候才确定,在定义/等于时并没有确定下来。
想使⽤变量的时候使⽤“$”来引⽤,如果不想看到命令时,可以在命令的前⾯加上”@”符号,则不会显⽰命令本⾝。
当我们执⾏make命令的时候,make这个指令本⾝,会把整个Makefile读进去,进⾏全部分析,然后解析⾥⾯的变量。常⽤的变量的定义如下:
:= // 即时变量
= // 延时变量
= // 延时变量, 如果是第⼀次定义才起效, 如果在前⾯该变量已定义则忽略这句
+= // 附加, 它是即时变量还是延时变量取决于前⾯的定义
=: // 如果这个变量在前⾯已经被定义,这句话就不会起效果。
实例:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论