文章目录
- 1. C 语言编译原理和详细过程
- 1.1 预处理阶段
- 1.2 编译阶段
- 1.3 汇编阶段
- 1.4 链接阶段
- 2. 疑问点解析
- 2.1 三地址码是什么?有什么作用
- 2.2 符号表是什么?有何作用
- 2.3 重定位的含义与作用
- 2.3 符号表和重定位在整个编译过程中的作用
- 2.4 动态链接库.so和静态链接库.a
- 2.5 不要混淆.o文件和.so文件
- 3. 知识补充
1. C 语言编译原理和详细过程
编译是将源代码转换为计算机可执行的二进制文件的过程,整个过程主要分为四个阶段: 预处理; 编译; 汇编; 链接。
1.1 预处理阶段
工具:预处理器
输入:.c 源文件
输出:.i 预处理后的文件
预处理阶段做的主要工作:
- 宏展开:将所有
#define
定义的宏进行文本替换 - 头文件包含:递归展开
#include
指令,将头文件内容插入源代码 - 条件编译:处理条件编译指令(如
#ifdef
等命令),根据条件保留或删除代码块(如调试代码) - 删除注释
1.2 编译阶段
工具:编译器
输入:.i文件
输出:.s汇编代码文件
编译阶段做的主要工作:
- 词法分析:将代码拆分为token(如标识符,关键字,运算符)
- 语法分析:构建抽象语法树(AST),检查语法是否符合C标准(如检查括号是否匹配,语句是否合法)
- 语义分析:检查类型匹配,变量声明,作用域规则
- 中间代码生成:生成与平台无关的中间表示(如三地址码)
- 代码优化:对中间代码进行优化。如删除冗余代码、常量折叠等
- 目标代码生成:将优化后的中间代码转换为目标平台的汇编代码
1.3 汇编阶段
工具:汇编器
输入:.s汇编文件
输出:.o目标文件,即二进制机器码
汇编阶段做的主要工作:
- 指令转换:将汇编代码逐行转换为机器码
- 生成目标文件:生成包含机器码、符号表、重定位信息的**.o文件**
1.4 链接阶段
工具:链接器
输入:多个.o目标文件+静态库.a文件
输出:可执行文件
链接阶段做的主要工作:
- 符号解析:解决跨文件的函数或变量引用。如main.o中调用printf函数,则需找到该函数在libc.a中的定义
- 重定位:合并所有目标文件的代码段、数据段,分配最终内存地址;修正符号表中的地址偏移量
- 库文件处理:
- 静态链接:将静态库.a代码直接复制到可执行文件中
- 动态链接:记录动态库.so的路径,运行时加载 - 生成可执行文件:生成符合操作系统格式的可执行文件
注:可执行文件分为 代码段(.text)、数据段(.data)、未初始化数据段(.bss) 等
经过这一系列过程,c源代码最终变成了操作系统可直接执行的二进制文件 运行时由加载器将其读入内存并执行。
2. 疑问点解析
2.1 三地址码是什么?有什么作用
三地址码(Three-Address Code,TAC)是编译器中常用的一种中间表示(Intermediate Representation, IR)形式,它将复杂的表达式和语句拆解为一系列简单的指令,每条指令最多包含三个操作数。它的核心目标是简化代码优化和目标代码生成的过程,同时保持与机器无关的特性。
每条指令仅包含一个操作(如赋值,运算,跳转等),最多涉及三个操作数,两个输入一个输出。常见的通用格式如下:
result = operand1 op operand2
2.2 符号表是什么?有何作用
符号表是编译器/汇编器生成的一种数据结构,记录了程序中所有符号的信息:
- 符号名称:如函数名、全局变量名称;
- 符号类型:函数、变量、静态/全局作用域等;
- 符号地址:在目标文件中的相对地址或内存地址。
符号分类:
- 全局符号:可被其他文件访问的符号,如extern变量、非static函数;
- 局部符号:仅在本文件内可见的符号,如static函数或变量;
- 外部符号:在本文件中使用但未定义的符号,如调用了其他文件中的函数。
在链接阶段,链接器通过符号表解析不同目标文件之间的符号引用,如函数调用、变量访问等;符号表包含了符号的地址和类型,支持调试器(如gdb)定位代码和变量(比如有时候调试的时候会导入符号表);动态链接库.so需要依赖符号表在运行时绑定函数地址。
2.3 重定位的含义与作用
重定位时链接器在合并多个目标文件时,修正符号引用地址的过程。目标文件.o中的代码和数据地址是临时地址(基于偏移量的),链接器需要将其调整为最终可执行文件的绝对地址。
重定位表:每个目标文件都有一个重定位表,记录了需要修正的位置及其规则:
- 需要修正的偏移量:在目标文件中的位置;
- 符号名称:需要修正为哪个符号的地址;
- 重定位类型:如何计算最终地址,如相对地址或绝对地址。
重定位的作用:
- 合并多目标文件:将分散在多个.o文件中的代码和数据分配到统一的内存布局中;
- 解析外部依赖:将未定义的符号绑定到库中的实际地址;
- 生成可执行文件:确保程序运行时,所有指令和数据的地址正确。
2.3 符号表和重定位在整个编译过程中的作用
编译阶段会生成符号表并记录重定位信息:
- 生成符号表:编译器为每个.c文件生成.o目标文件,包含符号表和代码;
- 记录重定位信息:编译器标记所有需要重定位的位置,如外部函数调用等。
链接阶段会进行符号解析以及地址分配与重定位:
- 符号解析:链接器会检查所有目标文件的符号表,确保每个符号有且仅有一个定义;
- 地址分配与重定位:链接器为所有符号分配最终地址,并修正代码中的引用。
即符号表是程序符号的地址簿,记录了符号的定义和引用;重定位是链接器的修正工具,确保程序可以正确访问所有符号。
2.4 动态链接库.so和静态链接库.a
动态链接库.so文件,是在程序运行时被加载的,多个程序可共享同一个库文件,节省内存和磁盘空间。
静态链接库.a文件,是在编译时被整合到可执行文件中,生成独立的可执行文件,不需要外部依赖。
静态链接库和动态链接库的主要区别:
特性 | 静态库(.a) | 动态库(.so) |
---|---|---|
链接方式 | 编译时直接嵌入到可执行文件中 | 程序运行时动态加载 |
文件体积 | 可执行文件体积较大(包含库代码) | 可执行文件体积较小(仅存引用) |
运行时依赖 | 无需外部库文件 | 必须存在对应的.so 文件 |
内存占用 | 每个进程独立加载库代码,内存冗余 | 多个进程共享同一份库代码,内存节省 |
更新维护 | 需重新编译整个程序 | 仅替换.so 文件即可更新库功能 |
加载速度 | 启动快(代码已嵌入) | 启动稍慢(需加载动态库) |
兼容性风险 | 无版本冲突问题 | 需保证.so 版本与程序兼容 |
常见使用场景 | 嵌入式系统、独立工具、无依赖部署 | 通用系统库(如libc )、多进程共享场景 |
示例:
-
静态链接:
gcc main.c -o program -L/path/to/libs -lstaticlib -static
-static选项表明强制静态链接所有库,包括库系统如libc
这样生成的program不依赖任何外部库。 -
动态链接:
gcc main.c -o program -L/path/to/libs -ldynamiclib
默认链接动态库(优先查找.so),运行时需确保动态库在系统路径或通过LD_LIBRARY_PATH指定。
-
混合链接:
gcc main.c -o program -Wl,-Bstatic -lstaticlib -Wl,-Bdynamic -ldynamiclib
-Wl,-Bstatic
:指定后续库静态链接。-Wl,-Bdynamic
:恢复为动态链接
2.5 不要混淆.o文件和.so文件
.o文件是目标文件,通常是编译单个源文件后的输出,包含机器码和符号表,但还未经过链接,所以可能有未解析的符号。
.so文件是动态链接库,是多个目标文件经过链接后生成的共享库,可以在运行时被多个程序共享。
.o文件是编译阶段的产物,.so文件是链接阶段的产物;.so文件在程序运行时加载,.o文件在链接时被合并到可执行文件或静态库中。
二者主要区别:
特性 | 目标文件(.o) | 动态链接库(.so) |
---|---|---|
生成阶段 | 编译阶段的产物(单个源文件编译后生成) | 链接阶段的产物(多个目标文件或源码链接生成) |
内容 | 包含单个源文件编译后的机器码、符号表、重定位信息 | 包含多个目标文件或源码的已链接代码,具有完整的符号解析和地址分配 |
用途 | 作为中间文件,供后续链接生成可执行文件或库 | 作为共享库,供程序在运行时动态加载 |
依赖关系 | 未解析的符号需在链接阶段解决 | 符号已完全解析,但需在运行时与主程序或其他库动态绑定 |
文件独立性 | 无法单独运行,需链接后使用 | 可独立存在,但需主程序调用或动态加载 |
内存共享 | 不共享,每个进程独立加载 | 多个进程可共享同一份.so 的代码段,节省内存 |
更新维护 | 修改后需重新编译和链接 | 更新.so 后,主程序无需重新编译(需接口兼容) |
.o文件,通过编译单个源文件生成,包含机器码、符号表和重定位信息。.o文件作为中间文件,以供后续链接器将所有.o文件合并为可执行文件或库(可被归档为静态库.a文件,本质上是多个.o的集合)
.so文件,通过链接多个目标文件或源码生成,包含完全链接的代码(所有的符号已解析,除非依赖其他动态库)、位置无关代码(代码可加载到任意内存地址运行)、导出符号表(声明库中可供外部调用的函数或变量)。.so文件是在程序运行时由动态链接器加载到内存的,多个程序可共享一份.so代码,减少内存占用,同时支持库的热更新(即替换.so文件后重启程序生效)
总结:
.o
文件是编译阶段的中间产物,用于后续链接;.so
文件是链接后的动态库,用于运行时共享。.o
文件聚焦单个模块的编译结果,.so
文件聚焦多模块的协作与动态加载。
3. 知识补充
【GCC】gcc编译学习
【GDB】gdb使用