C/C++编译器构成
C/C++编译器构成
C/C++编译器有 4 个基本组成部分:预处理器、编译器、汇编器和链接器,它们功能大致如下:
一般我们说编译器是指整套工具链,与其中一个组成部分重名。
- 预处理器 cpp:处理以 # 开头的行,将所需库代码插入源程序,进行宏替换等等。
- 编译器 ccl:进行语法检查,若语法无误则将源代码翻译成汇编代码。
- 汇编器 as:将汇编代码翻译成机器指令,并将这些指令打包成可重定位目标文件。
- 链接器 ld:将所有可重定位目标文件与必需的库一起链接生成可执行目标文件。
在编写 C/C++ 程序时,遇到的错误按时期可以划分为 3 类:
- 编译期间语法错误;
- 链接期间找到对应的静态库或动态库;
- 运行时错误。
相较而言,遇到编译期间语法错误最幸运,可以根据错误信息修改程序。
链接错误比较头疼,文件编译顺序错误,或者没有指定库路径,或者路径打错了,或者路径不存在,最玄学的还是路径正确库也存在,但就是编译不过,哈哈哈。
运行时错误最头疼,因为程序遇到这个错误一般都会崩溃而停止运行。我们写个人代码程序遇到这样的错误不需要慌张,插桩法,打断点,继承了调试功能的 IDE 都能提供帮助。可是,如果大公司的服务遇到运行时错误而终止了,“我一分钟几百万上下”这话就不是吹牛的了,那将会造成极大的经济损失,同时公司名誉受损。总之关于运行时错误如何查找诱因、如何规避、如何恢复是一门学问,我知之甚少。
实践演练
通过编译一个 main.cpp 文件观察整个流程。
#include <iostream>
using namespace std;
void test()
{
cout << "111" << endl;
}
int main()
{
printf("%x\n", test);
printf("%x\n", &test);
return 0;
}
我们不必记住每个组件的名称,只需要用 gcc(C++是g++)这个集成工具,通过指定选项就可以调用每个组件,如下表格所示。
选项 | 作用 | 例子 |
---|---|---|
-E | 只进行预处理,得到 .i 文件 | gcc -E xxx -o yyy.i |
-S | 只进行预处理和编译,得到 .s 文件 | gcc -S xxx -o yyy.s |
-c | 只进行预处理、编译和汇编,得到 .o 文件 | gcc -c xxx -o yyy.o |
当然如果要生成最终的可执行文件,就不带任何选项,如 gcc xxx -o run
- 预处理:
g++ -E main.cpp -o main.i
,本来 15 行的代码,得到的 .i 文件有 26010 行。
- 编译:
g++ -S main.i -o main.s
,当然也可以直接对 .cpp 文件进行g++ -S main.cpp -o main.s
,编译器自动完成了之前阶段的工作,结果是一样的,但是只有 181 行。
- 汇编:
g++ -c main.s -o main.o
,当然也可以直接对 .cpp 文件进行g++ -c main.cpp -o main.o
,编译器自动完成了之前阶段的工作,结果是一样的,此时生成的 .o 文件是二进制文件,不可以查看内容。
- 链接:
g++ main.o -o main
,当然也可以直接对 .cpp 文件进行g++ -c main.cpp -o main.o
,编译器自动完成了之前阶段的工作,结果是一样的,此时生成的文件是二进制文件,不可以查看内容,但是可以直接执行./main
。