我有一组 *.C 文件(嵌入式相关)。任何人都可以向我详细说明编译时涉及的步骤/过程(内部信息),然后链接以创建最终的可执行文件(我需要有关预处理器/编译器通常对 C src 代码执行什么的信息/步骤)

此外,我只想了解最终可执行文件的一般结构(例如:头文件后跟符号表等。)

如果有人之前已经讨论过同一主题,也请通知我。

__卡努

最佳答案

以 gcc 为例,我认为使用的选项是 -save-temps。

大致的步骤是对文件进行传递以拉入所有包含并创建一个基本上要解析的文件。现在很多工具都使用基于一组规则(bison、yacc、flex 等)运行的解析器,目标是解析 ascii,将您的程序变成一种非常广泛的汇编语言,因为缺乏更好的术语。

a = a + 1;

可以变成

加载名为 a 的变量,大小为 blah,类型为 unsigned foo
立即加载 1,大小等等,无符号
添加
存储结果a

然后有可能发生的优化,编译器中间语言可能有一个增量函数,并确定增量比 1 和加法的加载更好。最终这些优化都完成了,这个中间代码通过后端到达目标指令集。这通常作为汇编输出,然后被送入汇编器,汇编器将其转换为目标文件,并且可以进行目标特定的优化。然后目标文件被送入链接器,链接器将它们链接在一起。一个程序中的一个函数可能正在调用一个不在名为 bob 的目标文件中的函数,目标文件没有到达 bob 的地址或偏移量,它在那里留下了一个洞来插入地址,链接器的工作是连接所有其中,决定函数 bob 将在二进制文件中的位置(为其分配一个地址)然后找到所有调用 bob 的地方,当这些地方被放置在内存中时,插入允许调用 bob 所需的指令或地址,以便最终结果是一个可执行的二进制文件。

llvm 已经是 gcc 的竞争对手,它提供了对这个过程的良好可见性。您可以将 C 代码编译为中间代码。从我们的 bob 函数开始

unsigned int bob ( unsigned int a )
{
返回(a+1);
}

编译为位码

clang -c -o bob.bc -emit-llvm bob.c

将位码反汇编为人类可读的形式

llvm-dis bob.bc

这导致 bob.ll

定义 i32 @bob(i32 %a) nounwind {
入口:
%a.addr = alloca i32,对齐 4
存储 i32 %a, i32* %a.addr, 对齐 4
%tmp = 加载 i32* %a.addr,对齐 4
%add = 添加 i32 %tmp, 1
ret i32 %add
}

未优化的代码喜欢经常从内存中存储和提取,并且当传递到函数中时经常从堆栈中存储和提取。

除了让您轻松地看到幕后,llvm 还不错,因为您可以在任何级别进行优化、组合对象并在整个程序级别进行优化,而 gcc 将仅将您限制在文件或函数级别。所以我们可以优化这个位码。

opt -std-compile-opts bob.bc -o bob_opt.bc
llvm-dis bob_opt.bc

那些额外的存储和负载都消失了,函数的内容仍然存在。

定义 i32 @bob(i32 %a) nounwind readnone {
入口:
%add = 添加 i32 %a, 1
ret i32 %add
}

然后 llc 用于将其转换为所需目标的汇编程序

llc -march=arm bob.bc
猫鲍勃
...
鲍勃:@@鲍勃
@BB#0:@%entry
str r0, [sp, #-4]!
添加 r0, r0, #1
添加 sp, sp, #4
bx lr
...
llc -march=arm bob_opt.bc
猫 bob_opt.s
...
鲍勃:@@鲍勃
@BB#0:@%entry
添加 r0, r0, #1
bx lr
...

是的,那里有很多书。还有许多编译器等等。除了 llvm,Fabrice Bellard(是的 qemu 人)有一个 super 简单的编译器,几乎没有一个编译器可以生成一个中间文件,你可以检查 http://bellard.org/fbcc/ 被掩埋得几乎不为人知,有趣看看你是否刚刚进入编译器的胆量。此外,还有一个众所周知的 tcc http://bellard.org/tcc/ 这个特别没有通过汇编程序的后端,直接生成操作码以提高速度和实时(重新)编译。

关于c - C 编译涉及哪些内部过程?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/3812670/

10-13 05:44