编译和链接!编译器做了什么?

很久很久以前,在一个非常遥远的银河系...人们编写程序时,将所有的源代码都写在一个文件中,发展到后来一个程序源代码的文件长达数百万行,以至于这个地方的人类已经没有能力维护这个程序了。人们开始寻找新的办法,一场新的软件开发革命即将爆发...

预编译

预编译过程主要处理那些#开头的预编译指令。同时也会删除注释,添加行号和文件名(为后续编译时,产生错误和警告能显示行号)

$ gcc -E hello.c -o hello.i

生成的hello.i文件,类似于以下

//....(省略若干)
extern int __vsnprintf_chk (char * restrict, size_t, int, size_t,
const char * restrict, va_list);
# 412 "/usr/include/stdio.h" 2 3 4
# 2 "hello.c" 2
# 11 "hello.c"
int main()
{
printf("Hello world\n");
printf("%d\n", 10);

return 0;
}

编译

编译就是将预处理完毕的文件进行一系列的词法分析、语法分析、语义分析以及优化后产生相应的汇编代码。

$ gcc -S hello.i -o hello.s

生成的hello.s文件,类似以下

.section    __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 13
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
//....(省略)

汇编

使用汇编器,将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。

$ gcc -c hello.s -o hello.o

这里的hello.o如果使用vim打开的话,将是乱码哟~它叫“目标文件”只差链接这一步骤就可以成为可执行文件了。 既然vim不能查看的话,有个叫做objdump的工具可以

$ objdump -h hello.o

目标文件的相关内容如下,由于程序比较简单,只有TEXT(代码区)和DATA(数据区)。如果有未初始化的全局变量或者未初始化的局部静态变量,将会有BSS类型。 总的来说,代码段属于程序指令,而数据段和BSS段则属于程序数据。

hello.o:	file format Mach-O 64-bit x86-64

Sections:
Idx Name Size Address Type
0 __text 00000040 0000000000000000 TEXT
1 __cstring 00000011 0000000000000040 DATA
2 __compact_unwind 00000020 0000000000000058 DATA
3 __eh_frame 00000040 0000000000000078 DATA

番外——关于链接

链接过程主要包括了地址和空间分配符号决议重定位。链接的过程好比拼图,每个小块都是一个个的小模块,彼此之间互相独立,但是只有整体拼凑完整,程序才能跑起来。

那么既然彼此独立,他们是如何相互调用的呢?这就是链接的功劳了,或者说是链接器的作用,让你可以直接引用其他模块的函数和全局变量而无需知道他们的地址,链接的时候由链接器完成这个地址修正的过程(也被叫做重定位)。

[get] 静态链接将链接库的代码复制到可执行程序中,使得可执行程序体积变大。 [get] 动态链接指的是需要链接的代码放到一个共享对象中,共享对象映射到进程虚地址空间,链接程序记录可执行程序将来需要用的代码信息,根据这些信息迅速定位相应的代码片段。

References: [1] 程序员的自我修养——链接、装载与库 俞甲子 著 [2] 线上幽灵,C++ 源代码到可执行代码的详细过程