编译C语言的四个阶段
本文是从 The Four Stages of Compiling a C Program 直接翻译过来的。
了解编译是如何工作的,对写代码和调试都颇有用处。
编译C程序有几个过程。总的来说,这可以分为四个独立的过程:预处理,编译, 汇编,链接。传统的C编译器调用其它程序分别来处理不同的过程。
本文中,我将以下面的源代码为例为说明编译的每个阶段:
/* * "Hello, World!": A classic. */ #include <stdio.h> int main(void) { puts("Hello, World!"); return 0; }
1 预处理
编译的第一个阶段叫预处理。这个阶段中,以 #
开头的行都将被预处理器
当做预处理命令来解释。这些命令就是一个有自己语法和语义的宏语言。它就
是来减少源代码中的重复的。它通过提供内联文件的功能,定义宏和有条件的
省略代码来完成减少重复的功能。
在解释命令前,预处理器会提前做一些处理工作。包括合并连续行(以 \
结尾的行)和跳过注释。
打印预处理阶段的结果,给 cc
传 -E
参数就行了。
cc -E hello_world.c
对于前面“Hello,world”的例子来说,预处理器会生成头文件 stdio.h
的
内容,并与 hello_world.c
文件相结合,把它从开头解析出来:
[lines omitted for brevity] extern int __vsnprintf_chk (char * restrict, size_t, int, size_t, const char * restrict, va_list); # 493 "/usr/include/stdio.h" 2 3 4 # 2 "hello_world.c" 2 int main(void) { puts("Hello, World!"); return 0; }
2 编译
第二个阶段就叫编译是很让人疑惑的。在这个阶段,根据目标处理器的架构,预 处理器生成的代码将被转化为对应的汇编指令。它还是一种人可读懂的语言。
因为这个步骤的存在,使用C语言可以包括一些内联汇编指令,也可以选择不同 的汇编器。
一些编译器直接就集成了汇编器,这些汇编器可以直接生成机器码,避免了成在 中间的汇编指令,再调用汇编译来汇编的过程。
可以给cc传 -S
选项来保存汇编的结果:
cc -S hello_world.c
这就可以生成一个名为 hello_world.s
的文件,它包含了生成的汇编指令。
在 Ubuntu 16.04.3 LTS
中是这样的:
.file "hello_world.c" .section .rodata .LC0: .string "Hello, World!" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $.LC0, %edi call puts movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609" .section .note.GNU-stack,"",@progbits
3 汇编
汇编阶段,汇编器把汇编指令转化为机器码或者目标文件。输出就是真实的将会 被目标处理器执行的指令。
给cc传递 -c
选项可以保存汇编阶段的结果:
cc -c hello_world.c
执行上面指令可以生成一个名为 hello_world.o
的文件,它包括程序的目标
码。该文件的内容是二进制格式的,可以由 hexdump
或者 od
来查看:
hexdump hello_world.o od -c hello_world.o
4 链接
汇编阶段生成的目标码是由机器能读懂的指令组成的。但是,该程序的有些部分 是缺失或者乱序的。要生成最后可以执行的程序,当前生成的结果需要重新整理、 填补上缺失的部分。这个过程就叫链接。
链接器会重新整理目标码,让某个部分的函数可以成功地调用到另一个部分的函
数。它也会加入程序需要使用的库函数中的指令。在这个"Hello, World"程序中,
链接器需要为目标码添加 puts
函数。
该阶段的输出就是最后可以执行的程序了。当没有使用任何参数调用时,cc默认
把它取名为 a.out
。可以使用 -o
选项来命名为其它名字:
cc -o hello_world hello_world.c