g++ 编译器的内部工作原理,用于傻瓜
The inner workings of the g++ compiler, for dummies
我对使用 g++ 编译 C/C++ 代码时的编译阶段有一个非常基本的了解,但我想要确认、澄清和额外的智慧珍珠,请。
对于这组文件:
main.c
foo.h
foo.c
bar.h
bar.c
这些调用执行以下操作...
g++ -c foo.c
g++ -c bar.c
g++ -c main.c
头文件现在已添加到源文件中,所有这些 .c 文件都编译为 .o 文件。
g++ -o main.out main.o foo.o bar.o
现在,所有.o文件都链接到一个可执行文件中 - main.out
.
.c文件被编译成目标文件,然后链接到最终的二进制文件中。目标文件基本上是二进制文件的未最终确定部分(它们包含在.c文件中定义的函数等的编译机器代码(。
在编译期间,.c文件包括头文件,这些文件实际上只是在#include
指令所在的位置扩展。从这个意义上说,.c文件是独立的,不需要单独编译标头;它们都是单个翻译单元的一部分,该翻译单元转换为单个对象文件。
编译的第一步是运行预处理器;这是一个花哨的文本操纵器,可以处理以#
开头的所有行(因此,它执行#include
指令和条件#ifdef
的扩展等(。
然后,翻译单元的文本被标记化(这称为词法分析(:字节被转换为最简单的可识别标记,例如"."变成一个点,"++"变成一个"增量",关键字被识别,变量名称被解析为整个实体(标识符(。令牌仍然没有意义,但它们比字节流更容易使用。
下一个逻辑步骤称为句法分析,根据语言的语法(语法(将标记流转换为抽象结构。这是报告语法错误的地方。例如,int a = 3;
可以被解析为 declaration(sym(a(, expression(constint(3(((。
之后的下一个逻辑步骤是语义分析,它赋予句法结构以意义 - 例如,解析器可能会生成二十个具有相同名称的变量声明,但这在语义上毫无意义。此处报告了更多错误,例如,不会从所有控制路径返回的非 void 函数。
最后,还有一个代码生成步骤,它选择低级CPU指令来执行翻译单元的语义结构。这实际上是一个巨大的"步骤",可能包括在生成最终指令代码之前将语义数据结构(通常以抽象语法树或AST的形式(转换为较低级(中间(表示的进一步转换。
在实践中,其中一些传递是组合的(例如,标记化通常在句法分析阶段按需发生,也可能构建语义上有意义的符号表等(。还有各种优化(有些集成,一些在单独的传递中(贯穿始终。例如,我相信GCC将程序转换为SSA中间表示,以进行数据流分析,以实现更好的代码优化。
然后,生成的指令(全局变量和静态变量等(将转储到目标文件中。然后将目标文件链接到可执行文件中(全局变量的地址、其他中定义的函数、外部对象文件和动态/共享库此时解析并在最终代码中修复(。
这些都不是 gcc 特有的;这适用于大多数(所有?(C++(和 C(编译器。
- 1d 智能指针不适用于语法 (*)++
- 使用C++库在Android项目中修改gradle中的cmake参数,用于插入指令的测试
- 用于访问容器<T>数据成员的正确 API
- 重载操作程序时出错>>用于类中的字符串 memebr
- 如何防止 c++ 在从浮点型转换为双精度型(不适用于 IO)时添加额外的小数?
- C++中的cin.ignore()函数不适用于整个流
- 没有用于初始化C++中的变量模板的匹配构造函数
- 用于C++中带有数组和指针的循环
- 为什么它不适用于Visual 2019的原因
- 使用在用于SFINAE的void_t中具有参数的方法
- 在createdialog创建的窗口中捕获用于编辑控件的OnMouseMove消息
- 重载==不适用于二进制树
- Insert函数不适用于2 if语句C++
- 用于矢量处理的多个线程
- 使外部项目可用于find_package CMake
- 在子目录中使用target_sources()命令时用于单元测试(qtest)的项目结构
- 为什么模数运算符不适用于该代码
- 并行用于C++17中数组索引范围内的循环
- 将fold表达式与std::一起用于两个元组
- g++ 编译器的内部工作原理,用于傻瓜