是的,有很多很多的书在那里。和很多很多编译器,等等。除了llvm,Fabrice Bellard(是的qemu的人),有一个超级简单,几乎没有编译器,产生一个中间文件,你可以检查http://bellard.org/fbcc/,这是埋葬,使它几乎不为人所知,有趣的看,虽然如果你只是进入编译器的内脏。此外,还有一个众所周知的,TCCX 1 E1 F1 X特别地,该TCCX 1 E1 F1 X不具有经过汇编器的后端,为了速度和真实的(重新)编译,直接生成操作码。
一般来说,编译器的输出是一个目标文件,其中包含了相应源文件中函数的可执行代码。(编译器可以直接生成机器码,也可以生成汇编语言,然后通过单独的汇编程序将其转换为机器码)。目标文件还包含其他内容,如静态数据对象、外部符号定义和引用。 链接器的工作是获取一组目标文件,并匹配符号引用/定义。例如,如果a. c定义了一个函数a,b. c调用了a(),链接器需要修补b的目标代码,以“填充”正确的a地址。(这是一种过度简化的做法,因为还有动态加载、共享库、重定位等。) 没有一个标准的可执行文件格式;一些常见的格式包括.EXE(32或64位)、ELF和Mach-O。 这个article in Linux Journal给出了链接器和加载器如何工作的更详细的解释。
3条答案
按热度按时间r7xajy2e1#
以gcc为例,我认为要使用的选项是-save-temps。
大致的步骤是在文件上做一个传递,把所有的包含都拉进来,并创建一个要解析的文件。现在很多工具使用一个解析器,它运行在一组规则上(野牛,yacc,flex等),目标是解析ascii,把你的程序变成一种非常广泛的汇编语言,因为缺乏一个更好的术语。
可能变成
最后,这些优化完成,并且该中间代码通过后端到达目标指令集。这通常作为汇编输出,并且被馈送到汇编器,汇编器将其转换为目标文件,和目标特定的优化可以发生。然后目标文件被馈送到链接器,链接器将它们链接在一起。一个程序中的一个函数可能正在调用一个不在名为bob的目标文件中的函数,对象文件没有到达Bob的地址或偏移量,它在那里留下了用于插入地址的孔,并且链接器的工作是连接所有这些,确定函数Bob将位于二进制文件中的何处(为其分配地址),然后找到调用Bob的所有位置,并且当这些位置被放置在存储器中时,插入允许Bob被调用所需的指令或地址,使得最终结果是可执行的二进制文件。
llvm已经是gcc的竞争对手,它提供了对这个过程的良好可见性。你可以将C代码编译成一个中间体。从我们的bob函数开始
编译成位码
将位代码反汇编为人类可读的形式
结果是bob.ll。
非优化代码喜欢经常被存储和从内存中提取,并且当传递到函数时经常被存储和从堆栈中提取。
除了能让你轻松地看到幕后的情况之外,llvm还很不错,因为你可以在任何级别进行优化,可以合并对象,可以在整个程序级别进行优化,而gcc只会将你限制在文件或函数级别。
那些额外的存储和负载都消失了,功能的实质仍然存在。
然后使用llc将其转换为所需目标的汇编程序。
是的,有很多很多的书在那里。和很多很多编译器,等等。除了llvm,Fabrice Bellard(是的qemu的人),有一个超级简单,几乎没有编译器,产生一个中间文件,你可以检查http://bellard.org/fbcc/,这是埋葬,使它几乎不为人所知,有趣的看,虽然如果你只是进入编译器的内脏。此外,还有一个众所周知的,TCCX 1 E1 F1 X特别地,该TCCX 1 E1 F1 X不具有经过汇编器的后端,为了速度和真实的(重新)编译,直接生成操作码。
qacovj5a2#
如果你真的想知道它是如何工作的,我建议你读A Retargetable C Compiler。它将介绍构建C编译器的所有步骤(我相信这本书涵盖了
lcc
编译器)。f45qwnt83#
一般来说,编译器的输出是一个目标文件,其中包含了相应源文件中函数的可执行代码。(编译器可以直接生成机器码,也可以生成汇编语言,然后通过单独的汇编程序将其转换为机器码)。目标文件还包含其他内容,如静态数据对象、外部符号定义和引用。
链接器的工作是获取一组目标文件,并匹配符号引用/定义。例如,如果a. c定义了一个函数
a
,b. c调用了a()
,链接器需要修补b的目标代码,以“填充”正确的a
地址。(这是一种过度简化的做法,因为还有动态加载、共享库、重定位等。)没有一个标准的可执行文件格式;一些常见的格式包括.EXE(32或64位)、ELF和Mach-O。
这个article in Linux Journal给出了链接器和加载器如何工作的更详细的解释。