文档概述
说明:
1.文章由移远通信技术股份有限公司提供
2.以下内容包含了个人理解,仅供参考,如有不合理处,请联系笔者修改

1. 工具链组件总览

为了避免在后续章节中混淆“编译器” “链接器” “运行时库” “标准库”这些概念,先给出一张工程上最常用的总览表。评审、选型和问题定位时,可以先按这一章确定自己讨论的是哪一层。

1.1 编译器总览

类别 常见实现 典型平台 主要特点 主要短板
GNU 编译器 gccg++ Linux、嵌入式 Linux、交叉编译环境 生态成熟,兼容传统 GNU 项目能力强 复杂 C++ 诊断体验通常不如 clang
LLVM 编译器 clangclang++ Linux、Android、跨平台新项目 诊断质量高,便于接入 Sanitizer、LTO、静态分析 老项目、历史内联汇编适配成本可能偏高
Microsoft 编译器 cl.exe Windows 原生开发 与 Visual Studio、PDB、Windows SDK 深度集成 与 GNU/Unix 风格构建体系差异较大
其他专用编译器 armclang、IAR、Keil C/C++ 等 裸机、MCU、专用芯片平台 针对特定芯片和调试器优化深 通用性弱,生态封闭,迁移成本高

1.2 链接器总览

类别 常见实现 常见搭配 主要特点 主要短板
GNU 传统链接器 ld.bfd gcc + Binutils 兼容性强,历史项目覆盖广 大工程链接速度和诊断体验通常一般
GNU 高速链接器 gold gcc、部分 clang 工程 相比ld.bfd 更关注链接性能 新项目主流度下降,生态重心已不再集中于此
LLVM 链接器 lld clang/LLVM,也可配合部分 GNU 前端 链接速度快,和 LLVM 工具协同自然 切换时需验证历史脚本、链接选项和产物一致性
MSVC 链接器 link.exe cl.exe Windows 原生支持完整,PDB 体系成熟 跨平台构建一致性较弱

1.3 C 运行时总览

类别 常见实现 典型使用场景 主要特点 主要短板
GNU libc glibc 通用 Linux 发行版、服务器、桌面、兼容性优先场景 功能完整,Linux 用户态兼容性最好 体积偏大,轻量化和静态链接体验一般
轻量 libc musl 轻量 rootfs、容器底座、嵌入式 Linux 实现紧凑,部署边界清晰,静态链接友好 对部分假定glibc 语义的软件兼容性较弱
传统嵌入式 libc uClibc-ng 存量嵌入式 Linux 平台 资源占用较低 主流度下降,新项目生态不占优势
Windows CRT UCRT、MSVC CRT Windows 原生程序 与 Windows API、MSVC 工具链配合紧密 不适用于 Linux/Unix 运行时语境

1.4 C++ 标准库总览

类别 常见实现 常见搭配 主要特点 主要短板
GNU C++ 标准库 libstdc++ gcc,也常与 clang 混合使用 生态最广,大量 Linux 三方 C++ 组件默认基于它 ABI 边界要特别关注,混用时排查成本较高
LLVM C++ 标准库 libc++ clang/llvm 与 LLVM 体系协同自然,现代化工具链一致性好 存量 Linux 三方预编译件兼容面不如libstdc++
Microsoft C++ 标准库 MSVC STL Windows + MSVC 与 Windows 原生开发工具链集成紧密 不适合直接套用到 GNU/LLVM 的 Linux 工程

1.5 低层运行时与 ABI 支撑库总览

类别 常见实现 常见搭配 主要特点 主要短板
GCC 低层运行时 libgcc GNU 工具链 编译器辅助函数、异常和栈展开支持成熟 在 LLVM 全家桶里不是最统一的组合
LLVM 低层运行时 compiler-rt LLVM 工具链 和 Sanitizer、LTO、CFI 等能力协同更自然 切换时通常要联动评估其他 ABI 配套库
C++ ABI 支撑库 libc++abi libc++ + LLVM 工具链 为异常、RTTI、C++ ABI 提供基础支撑 不能脱离标准库和编译器单独讨论
栈展开库 libunwind LLVM,也可用于其他回溯场景 回溯、异常处理和崩溃分析能力重要组成 与异常模型、符号质量和调试信息强相关
Windows 低层运行时 VCRuntime Windows + MSVC cl.exelink.exe、PDB 体系配合成熟 平台耦合强,不适用于 Linux 工程

1.6 使用这张表时的注意点

  • 编译器、链接器、C 运行时、C++ 标准库/低层运行时至少是四个层次,不要混为一个概念。
  • gccclang 是编译器,不等于 glibcmusl
  • glibcmusl 是 C 运行时,不等于 ldlld
  • libstdc++libc++ 是 C++ 标准库,不等于编译器前端本身。
  • libgcccompiler-rtlibunwindlibc++abi 更接近低层运行时和 ABI 支撑库,不宜直接和标准库混讲。
  • 工程里很多兼容性问题,本质上不是“代码没写对”,而是这四层没有统一基线。

1.7 阅读指引

如果你的目标是快速获取信息,可以按下面的路径阅读:

  • 想先建立整体概念:看第 1 章和第 3 章,先把组件分层和编译流程建立起来。
  • 想知道常见落地方式:看第 5 章和第 6 章,先理解常见工具链,再看推荐组合。
  • 想比较 glibc/muslgcc/clang:直接看第 9 章。
  • 想做技术选型或方案评审:重点看第 10 章,尤其是决策矩阵。
  • 想做工程约束梳理:看第 11 章,确认优先级和验证边界。
  • 只需要引用总结结论:直接看第 12 章。

2. 文档目的

本文面向内部研发、平台移植和系统集成人员,目标不是罗列名词,而是把几个经常混在一起的概念拆开说清楚:

  • 编译到底在做什么。
  • 编译工具链由哪些部件组成。
  • 常见工具链各自适合什么场景。
  • 常见链接库为什么要选它,以及代价是什么。

在系统移植、根文件系统裁剪、ABI 兼容性排查、三方预编译件接入这些工作里,工具链选择通常不是“能编过就行”,而是会直接决定以下结果:

  • 目标二进制是否能在设备上跑起来。
  • 程序体积、启动时延和内存占用是否可控。
  • 调试、回溯、崩溃定位是否容易。
  • 后续版本升级时 ABI 是否稳定。
  • 安全加固、LTO、CFI、Sanitizer 等能力能否持续使用。

3. 编译的基本原理

从工程视角看,所谓"编译"不是单一步骤,而是一条把源代码转换为目标镜像中的可执行产物的处理链。

flowchart TD
    A["源代码"] --> B["预处理<br/>宏展开、头文件包含"]
    B --> C["编译前端<br/>词法/语法/语义分析"]
    C --> D["优化与后端<br/>中间表示转机器码"]
    D --> E["汇编<br/>生成目标文件"]
    E --> F["链接<br/>符号解析、段布局"]
    F --> G["可执行文件<br/>动态库/静态库"]
    G --> H["装载与运行时链接<br/>内存映射、动态依赖解析"]
    
    style A fill:#e1f5fe
    style G fill:#e8f5e8
    style H fill:#fff3e0

从工程视角看,所谓"编译"不是单一步骤,而是一条把源代码转换为目标镜像中的可执行产物的处理链。

典型流程如下:

源代码
  -> 预处理
  -> 编译前端
  -> 优化
  -> 目标代码生成
  -> 汇编
  -> 链接
  -> 可执行文件 / 动态库 / 静态库

3.1 预处理

预处理主要处理宏展开、头文件包含、条件编译、行控制等动作。

典型输入:

  • #include
  • #define
  • #if/#ifdef

典型输出:

  • 一个已经展开后的中间源码文本

工程上预处理的意义主要有两点:

  • 把平台差异集中在头文件和宏层面处理。
  • 在问题定位时,可以先确认"编译器实际看到的代码"是什么。

缺点也很明显:

  • 宏过多时,可读性和可调试性会明显下降。
  • 大量条件编译会导致不同产品形态之间的行为分叉。

3.2 编译前端

前端负责词法分析、语法分析、语义分析,并把源码转换为编译器内部中间表示。

前端关心的是语言规则是否成立,例如:

  • 语法是否正确。
  • 类型是否匹配。
  • 标识符是否可见。
  • 模板、内联、常量表达式是否满足约束。

这一阶段决定了语言兼容性上限。换句话说,某个编译器"支不支持某个 C++ 标准特性",本质上主要看前端能力。

3.3 优化与后端

优化器和后端负责把中间表示转成目标架构的高质量机器码。

典型优化包括:

  • 常量折叠
  • 死代码消除
  • 内联展开
  • 循环优化
  • 向量化
  • 跨模块优化

后端负责:

  • 指令选择
  • 寄存器分配
  • 调度
  • 调用约定落地
  • 目标文件生成

这一阶段直接影响:

  • 性能
  • 代码尺寸
  • 可回溯性
  • 特定 CPU 特性的利用程度

3.4 汇编

汇编器把汇编代码转成目标文件,通常是 .o.obj 这类可重定位文件。

目标文件里通常已经包含:

  • 代码段
  • 数据段
  • 符号表
  • 重定位信息
  • 调试信息

3.5 链接

链接是把多个目标文件和库合并成最终产物的过程,也是工程里最容易被低估的一步。

链接器主要完成:

  • 符号解析
  • 重定位
  • 段布局
  • 库选择
  • 入口点确定
  • 动态依赖记录

链接的输出可能是:

  • 可执行文件
  • 动态链接库
  • 静态库

链接阶段决定了很多运行时行为:

  • 最终依赖哪些共享库。
  • 使用静态链接还是动态链接。
  • 符号冲突如何处理。
  • 是否启用 PIE、RELRO、LTO 等能力。

3.6 装载与运行时链接

严格来说,这一步不属于编译器本体,但工程上必须纳入理解范围。

程序启动时,装载器和动态链接器会继续完成:

  • 把 ELF / PE / Mach-O 映射到内存。
  • 解析共享库依赖。
  • 完成 PLT / GOT 或等价机制初始化。
  • 调用全局构造、TLS 初始化等运行时逻辑。

很多"编译通过但设备上启动失败"的问题,根因并不在编译,而在运行时链接阶段,例如:

  • 缺失 DT_NEEDED 依赖。
  • libc 版本不匹配。
  • C++ ABI 不一致。
  • 运行时库搜索路径错误。

    4. 工具链的组成

一个完整的编译工具链通常至少包含以下部件。

4.1 编译器驱动

gccg++clangclang++

它本身不一定直接完成所有工作,更常见的是作为调度入口,把预处理、编译、汇编、链接串起来。

优点:

  • 使用入口统一。
  • 参数封装成熟。

缺点:

  • 出问题时容易把“驱动层问题”和“实际 linker / assembler 问题”混在一起。

4.2 汇编器

如 GNU as、LLVM integrated assembler。

优点:

  • 负责把低层指令落成目标文件。
  • 某些架构支持成熟,兼容传统汇编代码。

缺点:

  • 不同汇编器对语法、伪指令和调试信息细节支持存在差异。

4.3 链接器

如 GNU ld.bfdgold、LLVM lld、MSVC link.exe

优点:

  • 决定最终符号解析、段布局和产物格式。

缺点:

  • 链接器一旦选型不当,常见问题包括链接过慢、LTO 兼容差、错误信息晦涩。

4.4 归档工具

arranlib、LLVM llvm-ar

主要用于生成和维护静态库。

4.5 调试与分析工具

gdblldbaddr2linereadelfnmobjdump

这些工具不直接参与产物生成,但决定了问题是否能定位。

4.6 运行时与系统根

这部分经常被误以为"不属于工具链",实际它决定了最终 ABI。

包括:

  • libc
  • libstdc++ 或 libc++
  • libgcc 或 compiler-rt
  • libunwind
  • sysroot
  • 头文件和启动文件

如果编译器、链接器和运行时库不是同一套 ABI 假设,最终就会出现"能编、能链、不能跑"的问题。

```mermaid
graph TB
subgraph "编译器驱动"
A1["gcc/g++"] --> A2["clang/clang++"]
A2 --> A3["cl.exe"]
end

subgraph "核心工具链"
    B1["编译器前端"] --> B2["优化器"]
    B2 --> B3["后端"]
    B3 --> B4["汇编器"]
    B4 --> B5["链接器"]
end

subgraph "运行时与ABI支撑"
    C1["C运行时<br/>glibc/musl/UCRT"]
    C2["C++标准库<br/>libstdc++/libc++/MSVC STL"]
    C3["底层运行时<br/>libgcc/compiler-rt"]
    C4["ABI支撑库<br/>libc++abi/libunwind"]
end

subgraph "配套工具"
    D1["构建系统<br/>Make/CMake/Ninja"]
    D2["调试工具<br/>gdb/lldb"]
    D3["分析工具<br/>readelf/nm/objdump"]
end

A1 --> B1
A2 --> B1
A3 --> B1

B5 --> C1
B5 --> C2
B5 --> C3
B5 --> C4

D1 --> B1
D2 --> B5
D3 --> B5

style A1 fill:#e1f5fe
style A2 fill:#e1f5fe
style A3 fill:#e1f5fe
style B1 fill:#f3e5f5
style B5 fill:#f3e5f5
style C1 fill:#e8f5e8
style C2 fill:#e8f5e8
Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐