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

本文详细介绍了从源文件到可执行文件的四个阶段:预处理、编译、汇编和链接。预处理包括宏展开、条件编译等;编译涉及词法、语法和语义分析,生成汇编代码;汇编将汇编代码转换为机器指令;链接则将多个目标文件整合,解决符号引用和地址分配问题。此外,还概述了编译的具体流程和链接的模块拼接概念。
摘要由CSDN通过智能技术生成


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

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

/* hello.c */
#include <stdio.h>

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 <stdio.h> 的展开内容:

请添加图片描述

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

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

机器指令汇编代码
B82301mov ax,0123h
BB5604mov bx,0456h
03C3add ax,bx
03C0add ax,ax
B8004Cmov ax,4c00h
CD21int 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. 总结

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值