编译的四个阶段:
GNU项目包括,EMACS编辑器、GCC编译器、GDB调试器、汇编器、链接器、处理二进制文件的工具等。
就本质而言,gcc和g++并不是编译器,也不是编译器的集合,它们只是一种驱动器,根据参数中要编译的文件的类型,调用对应的GUN编译器而已。
gcc和g++的主要区别:
对于 *.c
和*.cpp
文件,gcc分别当做c和cpp文件编译(c和cpp的语法强度是不一样的)
对于 *.c
和*.cpp
文件,g++则统一当做cpp文件编译
使用g++编译文件时,g++会自动链接标准库STL,而gcc不会自动链接STL
gcc在编译C文件时,可使用的预定义宏是比较少的
在用gcc编译c++文件时,为了能够使用STL,需要加参数 –lstdc++ ,但这并不代表 gcc –lstdc++ 和 g++等价,它们的区别不仅仅是这个
1. 预处理
g++ -E -I /usr/local/include/ helloworld.cpp -o helloworld.i
预处理命令参数为 -E 预处理会寻找头文件,因此需要-I 指定include路径信息,预处理的过程不会自动生成文件,需要 -o
指定
2. 编译生成汇编
g++ -S helloworld.cpp
-S 完成了预处理和汇编的过程,未指定输出时,该过程会生成同名的汇编文件
3. 生成目标代码
g++ -c sayhello.cpp
-c 选项激活预处理,编译,和汇编,最后会生成同名的.o文件,该文件已经是机器代码
4. 链接生成可执行文件
ar -r libsay.a sayhello.o say.o
程序 ar 配合参数 -r 创建一个新库 libsay.a 并将命令行中列出的对象文件插入。采用这种方法,如果库不存在的话,参数 -r 将创建一个新的库,而如果库存在的话,将用新的模块替换原来的模块。
g++ boost_test.o -L /usr/local/lib
-L编译选项指定了链接的库所在目录
目标:目标分为最终目标(第一行就是最终目标)
依赖: 目标符号”:”后的都是依赖
命令:gcc为命令,他是GNU编译器,用于c/c++等编译
calc: main.c getch.c getop.c stack.c
gcc -o calc main.c getch.c getop.c stack.c
第一行冒号之前的calc,我们称之为目标(target),被认为是这条语句所要处理的对象,具体到这里就是我们所要编译的这个程序calc。冒号后面的部分(main.c getch.c getop.c stack.c),我们称之为依赖关系表,也就是编译calc所需要的文件,这些文件只要有一个发生了变化,就会触发该语句的第三部分,我们称其为命令部分,相信你也看得出这就是一条编译命令。
接下来,让我们来解决一下效率方面的问题:
cc = gcc
prom = calc
source = main.c getch.c getop.c stack.c
$(prom): $(source)
$(cc) -o $(prom) $(source)
我们在上述代码中定义了三个常量cc、prom以及source。它们分别告诉了make我们要使用的编译器、要编译的目标以及源文件。这样一来,今后我们要修改这三者中的任何一项,只需要修改常量的定义即可,而不用再去管后面的代码部分了。
但我们现在依然还是没能解决当我们只修改一个文件时就要全部重新编译的问题。而且如果我们修改的是calc.h文件,make就无法察觉到变化了(所以有必要为头文件专门设置一个常量,并将其加入到依赖关系表中)。 在标准的编译过程中,源文件往往是先被编译成目标文件,然后再由目标文件连接成可执行文件的。
cc = gcc
prom = calc
deps = calc.h
obj = main.o getch.o getop.o stack.o
$(prom): $(obj)
$(cc) -o $(prom) $(obj)
main.o: main.c $(deps)
$(cc) -c main.c
getch.o: getch.c $(deps)
$(cc) -c getch.c
getop.o: getop.c $(deps)
$(cc) -c getop.c
stack.o: stack.c $(deps)
$(cc) -c stack.c
现在上面的代码显得特别啰嗦,让我们再来做进一步的优化:
cc = gcc
prom = calc
deps = calc.h
obj = main.o getch.o getop.o stack.o
$(prom): $(obj)
$(cc) -o $(prom) $(obj)
%.o: %.c $(deps)
$(cc) -c $< -o $@
在这里,我们用到了几个特殊的宏。首先是%.o:%.c,这是一个模式规则,表示所有的.o目标都依赖于与它同名的.c文件(当然还有deps中列出的头文件)。
再来就是命令部分的$<
和$@
,其中$<
代表的是依赖关系表中的第一项,具体到我们这里就是%.c
。而$@
代表的是当前语句的目标,即%.o
。
到目前为止,我们已经有了一个不错的makefile,至少用来维护这个小型工程是没有什么问题了。当然,如果要进一步增加上面这个项目的可扩展性,我们就会需要用到一些Makefile中的伪目标和函数规则了。
cc = gcc
prom = calc
deps = $(shell find ./ -name "*.h")
src = $(shell find ./ -name "*.c")
obj = $(src:%.c=%.o)
$(prom): $(obj)
$(cc) -o $(prom) $(obj)
%.o: %.c $(deps)
$(cc) -c $< -o $@
clean:
rm -rf $(obj) $(prom)
shell函数主要用于执行shell命令,具体到这里就是找出当前目录下所有的.c和.h文件。而$(src:%.c=%.o)
则是一个字符替换函数,它会将src所有的.c字串替换成.o,实际上就等于列出了所有.c文件要编译的结果。有了这两个设定,无论我们今后在该工程加入多少.c和.h文件,Makefile都能自动将其纳入到工程中来。
有了最后两行,当我们在终端中执行make clean
命令时,它就会去删除该工程生成的所有编译文件。