【问题标题】:The inner workings of the g++ compiler, for dummiesg++ 编译器的内部工作原理,用于傻瓜
【发布时间】:2013-10-05 03:10:18
【问题描述】:

我对使用 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

【问题讨论】:

  • 我们不知道这些标题是做什么用的。
  • 头文件没有被包含那么多链接......也就是说,当预处理器正在读取一个文件时,当它遇到#include时,它会将.h文件插入到它的in-逐字记忆复制。换句话说,对于编译器来说,.c 文件中的 #include "bar.h" 行与使用文本编辑器在同一行插入 bar.h 的内容完全相同。
  • 另外,.o 文件已经编译(.o 代表目标文件)。下一步是将它们链接到一个可执行文件中。
  • 我想知道为什么我在这个问题上被否决了,这可能是从一个相对不成熟的角度来看,但我认为它提出了一些很好的问题和卡梅伦的一个很好的解释,这对我很有用并且可能对具有类似经验水平的其他人有用。有什么问题?

标签: c++ compiler-construction g++


【解决方案1】:

.c 文件被编译成目标文件,然后链接到最终的二进制文件中。目标文件基本上是未完成的二进制文件(它们包含 .c 文件中定义的函数的编译机器代码等)。

.c 文件,在编译过程中,include 头文件,基本上只是在#include 指令所在的位置展开。从这个意义上说,.c 文件是独立的,不需要单独编译头文件;它们都是单个翻译单元的一部分,该翻译单元被转换为单个目标文件。

编译的第一步是运行预处理器;这是一个花哨的文本操纵器,可以处理以# 开头的所有行(因此,它会扩展#include 指令和条件#ifdefs 等)。

然后,翻译单元的文本被标记化(这称为词法分析):字节被转换成最简单的可识别标记,例如'.'变成一个点,“++”变成一个单一的“增量”,关键字被识别,变量名被解析为整个实体(标识符)。标记仍然没有意义,但它们比字节流更容易使用。

下一个逻辑步骤,称为句法分析,根据语言的语法(句法)将标记流转换为抽象结构。这是报告语法错误的地方。例如,int a = 3; 可能被解析为声明(sym(a), expression(constint(3)))。

接下来的逻辑步骤是语义分析,它赋予句法结构以意义——例如,解析器可能会生成二十个同名的变量声明,但从语义上来说这是没有意义的.此处报告了更多错误,例如不从所有控制路径返回的非 void 函数。

最后是代码生成步骤,它选择低级 CPU 指令来执行翻译单元的语义结构。这实际上是一个巨大的“步骤”,可能包括将语义数据结构(通常以抽象语法树或AST的形式)进一步转换为较低级别(中间 em>) 在生成最终指令代码之前的表示。

在实践中,其中一些通道​​是组合的(例如,标记化通常在句法分析阶段按需发生,这也可能是构建具有语义意义的符号表等)。还有各种优化(一些集成,一些在单独的通道中)贯穿始终。例如,我相信 GCC 会将程序转换为 SSA 中间表示,以进行数据流分析,从而更好地优化代码。

然后将生成的指令、全局变量和静态变量等转储到目标文件中。然后将目标文件链接到一个可执行文件中(此时解析全局变量的地址、在其他文件中定义的函数、外部目标文件和动态/共享库并在最终代码中修复) .

这些都不是 gcc 所特有的;这适用于大多数(所有?)C++(和 C)编译器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-06
    • 2010-09-30
    • 1970-01-01
    • 1970-01-01
    • 2010-11-13
    • 1970-01-01
    相关资源
    最近更新 更多