(一)链接、装载与库 | 静态链接:编译和链接

文章目录

  • 1. 从源文件到可执行文件
    • 1.1 预处理
    • 1.2 编译
    • 1.3 汇编
    • 1.4 链接
  • 2. 编译的具体流程
    • 2.1 词法分析
    • 2.2 语法分析
    • 2.3 语义分析
    • 2.4 中间语言的生成
  • 3. 模块拼接——静态链接
  • 4. 总结


1. 从源文件到可执行文件

从源文件到可执行文件,主要经历了预处理编译汇编链接四个过程。以下面程序为例:

/* hello.c */
#include 

int main()
{
    printf("Hello World\n");
    return 0;
}

1.1 预处理

预处理将源代码文件 .c 或 .cpp 预编译为 .i 或 .ii 文件,使用 gcc / g++ 只进行预编译:

gcc -E hello.c -o hello.i
g++ -E hello.c -o hello.ii

预编译主要是做一些文本替换的工作,主要包括以下几个方面:

  • 将所有 #define 删除,并且展开所有宏定义
  • 处理所有条件编译指令,如 #if、#ifdef、#elif、#else、#endif
  • 处理 #include 预编译指令,将被包含文件插入到该指令的位置
  • 删除所有注释
  • 添加行号和文件名标识,以便于编译时产生错误或警告时显示行号
  • 保留所有 #pragma 编译器指令,因为编译器需使用它们

得到的 hello.i 文件中,最后几行为源代码,前面部分为 #inlcude 的展开内容:

(一)链接、装载与库 | 静态链接:编译和链接_第1张图片

1.2 编译

编译就是把预处理得到的文件进行一系列词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件,具体流程见下一节。使用 gcc 编译源文件:

gcc -S hello.c -o hello.s

上述源代码 hello.c 被编译为如下代码:

	.file	"hello.c"
	.text
	.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
	leaq	.LC0(%rip), %rdi
	call	puts@PLT
	movl	$0, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
	.section	.note.GNU-stack,"",@progbits

1.3 汇编

汇编器将汇编代码转变成机器可以执行的指令,每一条汇编代码几乎都对应于一条机器指令。使用 gcc 从源文件得到目标文件:

gcc -c hello.c -o hello.o

如一段常见汇编代码机器对应的机器指令:

机器指令 汇编代码
B82301 mov ax,0123h
BB5604 mov bx,0456h
03C3 add ax,bx
03C0 add ax,ax
B8004C mov ax,4c00h
CD21 int 21h

各机器指令以二进制的形式存放在 .o 文件中。

1.4 链接

链接器链接各目标文件后生成可执行文件,后文会详细介绍静态链接和动态链接的内容。gcc 的 --verbose 打印的链接的详细信息:

/usr/lib/gcc/x86_64-linux-gnu/7/collect2 
-plugin /usr/lib/gcc/x86_64-linux-gnu/7/liblto_plugin.so 
-plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper 
-plugin-opt=-fresolution=/tmp/ccIZXkkG.res 
-plugin-opt=-pass-through=-lgcc 
-plugin-opt=-pass-through=-lgcc_s 
-plugin-opt=-pass-through=-lc 
-plugin-opt=-pass-through=-lgcc 
-plugin-opt=-pass-through=-lgcc_s 
--build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed 
-dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o hello 
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o 
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o 
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o 
-L/usr/lib/gcc/x86_64-linux-gnu/7 
-L/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu 
-L/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib 
-L/lib/x86_64-linux-gnu -L/lib/../lib 
-L/usr/lib/x86_64-linux-gnu 
-L/usr/lib/../lib 
-L/usr/lib/gcc/x86_64-linux-gnu/7/../../.. /tmp/ccrk6Z5i.o 
-lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state 
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o 
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o

2. 编译的具体流程

2.1 词法分析

词法分析器以源代码程序为输入,产生一系列单词符号,并将其归类:关键字、标识符、字面量或特殊符号。

2.2 语法分析

语法分析器将对由词法分析器产生的单词符号进行语法分析,从而产生语法树。

2.3 语义分析

语法信息仅完成对表达式语法的分析,不了解语句的真正含义。编译器能分析的语义是静态语义,如表达式类型;动态语义在运行期才能确定。

2.4 中间语言的生成

编译器分为前端和后端,前端负责产生机器无关的中间代码,后端将中间代码转换成目标机器代码。


3. 模块拼接——静态链接

模块化是当前程序设计的主流方法,它不仅可以将复杂系统分解为小系统,还能加快各模块的生成。把每个源代码模块独立编译,然后将它们组装起来,这个过程称为链接。链接的主要任务就是把各个模块间的引用处理好,使得各模块正确地衔接。链接主要包括地址和空间分配、符号决议和重定位等步骤。


4. 总结

本文简要介绍了编译和链接的基础知识,详细内容可查看编译原理相关书籍。


你可能感兴趣的:(程序员的自由修养——链接,装载与库,操作系统)