编译链接过程讲解

c/c++程序从原代码到二进制的可执行文件,分为预处理--编译--汇编--链接四个阶段。

一、整体流程概览

cpp 复制代码
//一步完成
gcc -o hello hello.c

//分步完成
gcc -E hello.c -o hello.i  //预处理
gcc -s hello.i -o hello.s  //编译
gcc -c hello.s -o hello.o  //汇编
gcc hello.o -o hello       //链接

二、分阶段详解

阶段 1:预处理

  • 输入.c 源代码文件

  • 输出.i 预处理后的 C 文件

  • 核心操作

    1. 展开所有#define宏定义,执行宏替换
    2. 递归展开所有#include头文件(将头文件内容直接插入到当前文件)
    3. 处理所有条件编译指令(#if/#ifdef/#endif等)
    4. 删除所有注释(单行//和多行/* */
    5. 添加行号和文件名标识(用于编译错误和调试信息)
  • 面试高频考点

    • #include <file.h>#include "file.h"的区别:前者搜索系统头文件目录,后者先搜索当前目录,再搜索系统目录
    • 宏定义#definetypedef的本质区别:宏是文本替换,typedef 是类型别名
    • 宏的副作用:比如#define MAX(a,b) ((a)>(b)?(a):(b)),调用MAX(i++,j)会导致 i 被自增两次

阶段 2:编译

  • 输入.i 预处理后的 C 文件

  • 输出.s 汇编语言文件

  • 核心操作:将 C 代码翻译成汇编指令,是整个过程中最复杂的阶段,分为 6 个子步骤:

    1. 词法分析:将源代码拆分成一个个 token(关键字、标识符、常量、运算符等)
    2. 语法分析:根据语法规则生成抽象语法树(AST)
    3. 语义分析:检查语法树的语义正确性(类型检查、类型转换等)
    4. 中间代码生成:将语法树转换成与平台无关的中间代码(如三地址码)
    5. 代码优化:对中间代码进行优化(常量折叠、死代码消除、循环展开、内联等)
    6. 目标代码生成:将优化后的中间代码转换成特定平台的汇编指令
  • 面试高频考点

    • 编译器优化的常见类型及作用
    • 内联函数和宏的区别:内联函数时编译的时候,有类型检查,宏是预处理的时候展开,五类型检查

阶段 3:汇编(Assembly)

  • 输入.s 汇编语言文件
  • 输出.o 可重定位目标文件(ELF 格式,Linux 下)
  • 核心操作:将汇编指令逐条翻译成机器指令(二进制代码),生成目标文件。

阶段 4:链接(Linking)【字节跳动面试核心中的核心】

  • 输入 :多个.o目标文件 + 静态库 / 动态库
  • 输出:可执行文件(ELF 格式)
  • 核心作用
    1. 解决多个目标文件之间的符号引用问题(比如 A 文件调用 B 文件的函数)
    2. 进行重定位,将目标文件中的相对地址转换为最终的虚拟地址
    3. 合并相同类型的段(比如所有目标文件的.text段合并成一个大的.text段)

链接分为静态链接动态链接两种,下面分别详解。


4.1 静态链接(Static Linking)

静态链接在编译链接阶段 完成,将所有需要的目标文件和静态库(.a文件)打包成一个独立的可执行文件。

4.1.1 步骤 1:符号解析(Symbol Resolution)
  • 什么是符号:函数名、全局变量名、静态变量名
  • 符号表:每个目标文件都有一个符号表,记录了该文件定义的符号和引用的外部符号
  • 符号解析的目的:为每个外部符号引用找到对应的定义
强符号与弱符号规则
  • 强符号:已初始化的全局变量、函数定义
  • 弱符号 :未初始化的全局变量、用__attribute__((weak))修饰的函数 / 变量
  • 规则
    1. 不允许有多个同名的强符号(否则报multiple definition错误)
    2. 如果一个强符号和一个弱符号同名,选择强符号
    3. 如果有多个同名的弱符号,选择占用空间最大的那个
4.1.2 步骤 2:重定位(Relocation)
  • 为什么需要重定位:目标文件中的地址都是相对地址(相对于自身段的起始地址),不知道最终会被加载到内存的哪个位置
  • 重定位的过程:
    1. 链接器为每个段分配最终的虚拟地址
    2. 遍历所有重定位条目,根据重定位类型修改指令中的地址
    3. 将修改后的指令写入最终的可执行文件
4.1.3 静态链接的优缺点
  • 优点:
    1. 运行速度快,不需要在运行时进行链接
    2. 可执行文件独立,不依赖系统的库文件
  • 缺点:
    1. 可执行文件体积大,包含了所有用到的库代码
    2. 内存浪费:多个进程运行同一个静态链接的程序,会在内存中加载多份相同的库代码
    3. 更新困难:库更新后,所有使用该库的程序都需要重新编译链接

4.2 动态链接(Dynamic Linking)

动态链接将链接过程推迟到程序运行时 进行,多个进程可以共享同一个动态库(.so文件)的代码段。

4.2.1 基本原理
  • 编译链接时,链接器不将动态库的代码复制到可执行文件中,而是在可执行文件中记录动态库的名称和符号信息
  • 程序运行时,由操作系统的动态链接器(ld-linux.so)加载动态库,并完成符号解析和重定位
4.2.2 :PIC(位置无关代码)
  • 什么是 PIC:编译动态库时必须使用-fPIC选项生成位置无关代码,使得动态库的代码段可以被加载到内存的任意位置,而不需要修改代码本身
  • 为什么需要 PIC:如果动态库不是 PIC,那么每个进程加载动态库时都需要对代码段进行重定位,导致代码段无法共享,失去了动态链接的意义
  • PIC 的实现原理:通过 **GOT 表(全局偏移表)PLT 表(过程链接表)** 实现
4.2.3 :GOT 表与 PLT 表及延迟绑定机制

这是字节跳动面试动态链接部分最高频的考点,必须能清晰描述整个流程。

  • GOT 表(Global Offset Table)

    • 位于数据段,是一个数组,每个条目存储一个外部符号的绝对地址
    • 编译时,所有对外部符号的引用都被转换为对 GOT 表中对应条目的引用
  • PLT 表(Procedure Linkage Table)

    • 位于代码段,是一个数组,每个条目是一段小的汇编代码
    • 用于实现延迟绑定(惰性绑定):只有当函数第一次被调用时,才会解析该函数的地址并写入 GOT 表
  • 延迟绑定的完整流程(以调用printf为例)

    1. 程序第一次调用printf时,跳转到 PLT 1printf对应的 PLT 条目)
    2. PLT 1 跳转到 GOT 3printf对应的 GOT 条目)
    3. 此时 GOT 3 中存储的是 PLT 1 的下一条指令的地址,所以又跳回 PLT 1
    4. PLT 1printf的符号索引压入栈,然后跳转到 PLT 0
    5. PLT 0 将 GOT 1(动态链接器的标识)压入栈,然后跳转到 GOT 2(动态链接器的_dl_runtime_resolve函数地址)
    6. _dl_runtime_resolve函数解析printf的绝对地址,将其写入 GOT 3,然后跳转到printf函数执行
    7. 程序第二次及以后调用printf时,直接跳转到 GOT 3,此时 GOT 3 中已经存储了printf的绝对地址,不需要再进行解析
4.2.4 动态链接的两种方式
  1. 加载时动态链接:程序启动时,由动态链接器自动加载所有需要的动态库并完成绑定
  2. 运行时动态链接 :程序运行过程中,通过dlopen()/dlsym()/dlclose()函数手动加载和卸载动态库,适用于插件系统等场景
4.2.5 动态链接的优缺点
  • 优点:
    1. 可执行文件体积小,只包含必要的代码
    2. 内存共享:多个进程可以共享同一个动态库的代码段,节省内存
    3. 更新方便:动态库更新后,不需要重新编译所有使用该库的程序
  • 缺点:
    1. 运行速度稍慢,第一次调用函数时需要进行符号解析
    2. 可执行文件依赖系统的动态库,移植性较差
相关推荐
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪1 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux
小宇宙Zz2 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工2 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信