C编译涉及哪些内部过程?

C编译涉及哪些内部过程?,c,compiler-construction,embedded,linker,C,Compiler Construction,Embedded,Linker,我有一组*.C文件(嵌入式相关)。有人可以向我详细说明编译过程中涉及的步骤/过程(内部信息),然后链接创建最终的可执行文件(我需要关于预处理器/编译器通常对C src代码执行什么的信息/步骤) 另外,我只想了解最终可执行文件的一般结构(例如:头文件后面跟着符号表等等) 如果有人之前已经讨论过同样的话题,也请通知我 __卡努(Kanu)对于一个如此深奥的问题来说,这可能太深奥了。如果你真的需要知道这一切是如何运作的,我建议你阅读。它将经历构建C编译器的所有步骤(我相信这本书涵盖了lcc编译器)。一

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

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

如果有人之前已经讨论过同样的话题,也请通知我


__卡努(Kanu)

对于一个如此深奥的问题来说,这可能太深奥了。如果你真的需要知道这一切是如何运作的,我建议你阅读。它将经历构建C编译器的所有步骤(我相信这本书涵盖了
lcc
编译器)。

一般来说,编译器的输出是一个目标文件,其中包含相应源文件中函数的可执行代码。(编译器可以直接生成机器代码,也可以生成汇编语言,通过单独的汇编过程将其转换为机器代码)。对象文件还包含其他内容,如静态数据对象、外部符号定义和引用

链接器的工作是获取一组对象文件并匹配符号引用/定义。例如,如果a.c定义了一个函数
a
,而b.c调用了
a()
,那么一旦知道
a
的正确地址,链接器就需要修补b的目标代码,以“填充”该地址。(这是一个严重的过度简化,因为还有动态加载、共享库、重新定位等。)

没有一种标准的可执行文件格式;一些常见的格式包括.EXE(32或64位)、ELF和Mach-O


这对链接器和加载程序的工作原理给出了更详细的解释。

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

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

a = a + 1; a=a+1; 可能变成

Load variable named a, size of blah, type unsigned foo load immediate 1, size blah, unsigned add store result a 加载名为a的变量,大小为blah,类型为unsigned foo 立即加载1,大小无效,未签名 添加 存储结果a 然后可能会发生优化,编译器中间语言可能有一个increment函数,并确定increment优于1和add的加载。最终这些优化都完成了,这个中间代码通过后端到达目标指令集。这通常作为程序集输出,并输入到汇编程序中,汇编程序将其转换为目标文件,并且可以进行特定于目标的优化。然后对象文件被输入链接器,链接器将它们链接在一起。一个程序中的一个函数可能正在调用一个不在名为bob的目标文件中的函数,该目标文件没有到达bob的地址或偏移量。它在那里留下了一个洞供插入地址,链接器的任务是连接所有这些,决定bob将在二进制文件中的哪个位置(为其分配地址)然后找到所有调用bob的地方,当这些地方被放入内存时,插入允许调用bob所需的指令或地址,以便最终结果是一个可执行的二进制文件

llvm已经是gcc的竞争对手,可以很好地了解这个过程。您可以将C代码编译成中间代码。从我们的bob函数开始

unsigned int bob ( unsigned int a ) { return(a+1); } 无符号整数bob(无符号整数a) { 回报率(a+1); } 编译为位码

clang -c -o bob.bc -emit-llvm bob.c clang-c-obb.bc-emit llvm bob.c 将位代码反汇编为人类可读的形式

llvm-dis bob.bc llvm dis bob.bc 这导致了bob.ll

define i32 @bob(i32 %a) nounwind { entry: %a.addr = alloca i32, align 4 store i32 %a, i32* %a.addr, align 4 %tmp = load i32* %a.addr, align 4 %add = add i32 %tmp, 1 ret i32 %add } 定义i32@bob(i32%a)风{ 条目: %a、 地址=alloca i32,对齐4 存储i32%a,i32*%a.addr,对齐4 %tmp=加载i32*%a.addr,对齐4 %添加=添加i32%tmp,1 ret i32%添加 } 非优化代码喜欢经常从内存中存储和提取,并且在传递到函数时经常从堆栈中存储和提取

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

opt -std-compile-opts bob.bc -o bob_opt.bc llvm-dis bob_opt.bc opt-std compile opts bob.bc-o bob_opt.bc llvm dis bob_opt.bc 那些额外的存储和装载都消失了,功能的实质仍然存在

define i32 @bob(i32 %a) nounwind readnone { entry: %add = add i32 %a, 1 ret i32 %add } 定义i32@bob(i32%a)和Wind readnone{ 条目: %添加=添加i32%a,1 ret i32%添加 } 然后使用llc将其转换为所需目标的汇编程序

llc -march=arm bob.bc cat bob.s ... bob: @ @bob @ BB#0: @ %entry str r0, [sp, #-4]! add r0, r0, #1 add sp, sp, #4 bx lr ... llc -march=arm bob_opt.bc cat bob_opt.s ... bob: @ @bob @ BB#0: @ %entry add r0, r0, #1 bx lr ... llc-march=arm bob.bc 猫鲍勃 ... 鲍勃:@@鲍勃 @BB#0:@%条目 STRR0,[sp,#-4]! 加上r0,r0,#1 添加sp、sp、#4 bx-lr ... llc-march=arm bob_opt.bc 猫bob_opt.s ... 鲍勃:@@鲍勃 @BB#0:@%条目 加上r0,r0,#1 bx-lr ...
是的,外面有很多书。还有许多编译器,等等。除了llvm之外,Fabrice Bellard(是qemu的人)有一个超级简单的编译器,几乎没有一个编译器可以生成一个中间文件,你可以检查这个文件,它被隐藏起来,几乎不为人所知,但是如果你刚刚进入编译器的核心,看看它会很有趣。此外,还有一个众所周知的tcc,它没有经过汇编程序的后端,操作码是直接生成的,用于速度和实时(重新)编译。

Ok。但是我们可以通过一些一般的步骤吗?谢谢,我不是询问者,但是我发现这个答案非常有用。