0

我有一个基本的了解,但我希望得到确认、澄清和额外的智慧。

对于这组文件:

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.

4

1 回答 1

9

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

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

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

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

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

之后的下一个逻辑步骤是语义分析,它为句法结构赋予意义——例如,解析器可能会生成 20 个具有相同名称的变量声明,但在语义上这是没有意义的。这里报告了更多错误,例如不从所有控制路径返回的非空函数。

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

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

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

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

于 2013-10-05T03:18:03.043 回答