以下内容源于C语言中文网的学习与整理,非原创,如有侵权请告知删除。
一、编译的流程
编译C/C++ 程序,是指将C/C++源代码转变为可执行程序。
这需要经历4个过程:预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)、链接(Linking)。
二、一步完成编译
GCC 编译器并未提供给用户可用鼠标点击的界面窗口,要想调用 GCC 编译器编译 C 或者 C++ 程序,只能通过执行相应的 gcc 或者 g++ 指令。使用 GCC 编译器编译 C 或者 C++ 程序,也必须要经历上述的 4 个过程。但考虑在实际使用中,用户可能并不关心程序的执行结果,只想快速得到最终的可执行程序,因此 gcc 和 g++ 都对此需求做了支持。
如下所示,运行C文件demo.c与C++文件demo.cpp的一步完成编译的指令,GCC 编译器就会在当前目录下生成对应的可执行文件,该文件的名称为 a.out,或者使用-o 选项指定要生成的文件名。
xjh@ubuntu:~/iot/tmp$ gcc demo.c
xjh@ubuntu:~/iot/tmp$ g++ demo.cpp #或者 gcc -xc++ -lstdc++ -shared-libgcc demo.cpp
xjh@ubuntu:~/iot/tmp$ gcc demo.c -o demo.exe
xjh@ubuntu:~/iot/tmp$ g++ demo.cpp -o democpp.exe # 或者 gcc -xc++ -lstdc++ -shared-libgcc demo.cpp -o democpp.exe
虽然仅有一条指令,但依然按照预处理、编译、汇编、链接的过程将 C 、C++ 程序转变为可执行程序的。
那些本应在预处理阶段、编译阶段、汇编阶段生成的中间文件,此执行方式默认是不会生成的,只会生成最终的 a.out 可执行文件(除非为 gcc 或者 g++ 额外添加 -save-temps 选项)。
xjh@ubuntu:~/iot/tmp$ ls
demo.c
xjh@ubuntu:~/iot/tmp$ gcc demo.c
xjh@ubuntu:~/iot/tmp$ ls
a.out demo.c
xjh@ubuntu:~/iot/tmp$ gcc demo.c -save-temps
xjh@ubuntu:~/iot/tmp$ ls
a.out demo.c demo.i demo.o demo.s
xjh@ubuntu:~/iot/tmp$
三、分步编译程序
表1列出了实际使用 gcc 或者 g++ 指令编译 C/C++ 程序时,常用的一些指令选项。
gcc/g++指令选项 | 功 能 |
---|---|
-E | 预处理指定的源文件,不进行编译。 |
-S | 编译指定的源文件,不进行汇编。 |
-c | 编译、汇编指定的源文件,不进行链接。 |
-o | 指定生成文件的文件名。 |
-llibrary 或 -I library | 其中 library 表示要搜索的库文件的名称。该选项用于手动指定链接环节中程序可以调用的库文件。建议 -l 和库文件名之间不使用空格,比如 -lstdc++。 |
-L/pathname | 该选项用于为GCC添加另一个搜索链接库的目录,其中pathname是链接库的路径。 |
-ansi | 对于 C 语言程序来说,其等价于 -std=c90;对于 C++ 程序来说,其等价于 -std=c++98。 |
-std= | 手动指令编程语言所遵循的标准,例如 c89、c90、c++98、c++11 等。 |
下面将以某个demo.cpp 源文件来说明如何分步编译一个C++程序。
1、预处理
通过给 g++ 指令添加 -E 选项,可实现令 GCC 编译器只对目标源程序进行预处理操作。
预处理,其实就是对各种预处理命令进行处理,包括头文件的包含、宏定义的扩展、条件编译的选择、去掉空行与注释等等。
由于编译阶段需要用到预处理的结果,因此这里必须使用 -o 选项,将该结果输出到指定的 demo.i 文件中(Linux 系统中,通常用 ".i" 或者 ".ii" 作为 C++ 程序预处理后所得文件的后缀名)。
xjh@ubuntu:~/iot/tmp$ ls
demo.cpp
xjh@ubuntu:~/iot/tmp$ g++ -E demo.cpp -o demo.i
xjh@ubuntu:~/iot/tmp$ ls
demo.cpp demo.i
xjh@ubuntu:~/iot/tmp$
2、编译
通过给 g++ 指令添加 -S 选项,即可令 GCC 编译器仅对指定预处理文件做编译操作。
编译,其实就是对 demo.i 文件做进一步的语法分析,并生成对应的汇编代码文件(Linux 发行版通常以 ".s" 作为其后缀名)。
即便不使用 -o 选项,编译结果也会输出到和预处理文件同名(后缀名改为 .s)的新建文件中。
xjh@ubuntu:~/iot/tmp$ g++ -S demo.i
xjh@ubuntu:~/iot/tmp$ ls
demo.cpp demo.i demo.s
xjh@ubuntu:~/iot/tmp$
3、汇编
通过给 g++ 指令添加 -c 选项,即可令 GCC 编译器仅对指定的汇编代码文件做汇编操作。
汇编阶段就是将之前生成的汇编代码文件(demo.s)做进一步转换,生成对应的机器指令。
默认情况下汇编操作会自动生成一个和汇编代码文件名称相同、后缀名为 .o 的二进制文件(又称为目标文件)。
xjh@ubuntu:~/iot/tmp$ g++ -c demo.s
xjh@ubuntu:~/iot/tmp$ ls
demo.cpp demo.i demo.o demo.s
xjh@ubuntu:~/iot/tmp$
4、链接
目标文件已经是二进制文件,与可执行文件的组织形式类似,只是有些函数和全局变量的地址还未找到,因此还无法执行。链接的作用就是找到这些目标地址,将所有的目标文件组织成一个可以执行的二进制文件。它必须把符号(变量名、函数名等一些列标识符)用对应的数据的内存地址(变量地址、函数地址等)替代,以完成程序中多个模块的外部引用。
完成链接操作并不需要给 g++ 添加任何选项,只要将汇编阶段得到的 demo.o 作为参数传递给它,g++就会在其基础上完成链接操作(GCC 默认会链接 libc.a 或者 libc.so,但是对于其他的库,例如非标准库、第三方库等,就需要手动添加。)
如果不使用 -o 选项将执行结果输出到指定文件,则默认创建一个名为 a.out 的可执行文件,并将执行结果输出到该文件中。
xjh@ubuntu:~/iot/tmp$ g++ demo.o
xjh@ubuntu:~/iot/tmp$ ls
a.out demo.cpp demo.i demo.o demo.s
xjh@ubuntu:~/iot/tmp$ g++ demo.o -o demo.exe
xjh@ubuntu:~/iot/tmp$ ls
a.out demo.cpp demo.exe demo.i demo.o demo.s
xjh@ubuntu:~/iot/tmp$