标签: GCC

  • GCC、Clang、LLVM 到底是什么关系

    GCC、Clang、LLVM 到底是什么关系

    lihao · 发布: · 技术文章

    LLVM 与异构编译器 · S1 · 第 02 期

    适合能读基本 C++、会使用 Linux 终端的读者。视频制作中。

    演示范围:Linux x86-64、Clang/LLVM 21.1.8;具体环境与验证边界见文末说明。

    知识体系 / 编译器与 LLVM 全景

    如果把 GCC、Clang、LLVM 简单画成“GCC 是编译器,Clang 是前端,LLVM 是后端”,很快就会遇到矛盾:GCC 难道没有后端?LLVM 只有后端吗?clang++ 为什么还能负责链接?使用 Clang 时,为什么错误信息里会出现 GCC 安装路径?

    问题不在这些名字太复杂,而在于我们混合了几个不同层次:项目、命令行 driver、语言前端、优化与代码生成基础设施、链接器,以及标准库和运行时。

    本文先厘清组件关系,再结合命令观察其边界。计划输出、历史实测与本次资料补充分别说明。

    一、先给出最短答案

    • GCC 是 GNU Compiler Collection,一套完整的编译器系统。它有多语言前端、自己的中间表示与优化,也有多目标代码生成后端。
    • LLVM 是一个大型编译器与工具项目。LLVM Core 提供 LLVM IR、分析、优化和目标代码生成等基础设施;整个 LLVM Project 还包含 Clang、LLD、libc++、compiler-rt、MLIR 等子项目。
    • Clang 是 LLVM Project 中面向 C 语言家族的前端与工具基础设施,同时提供 clang、clang++ 和 clang-cl 等 compiler driver。

    所以,GCC 与“Clang + LLVM Core”常常是可以比较的两套编译器实现;Clang 与 LLVM 则是同一项目生态中不同边界的组件。这个说法仍是简化,但已经避免了最常见的错误。

    二、五个层次不能混在一起

    层次 GCC 生态中的常见对象 LLVM 生态中的常见对象
    项目 GNU Compiler Collection LLVM Project
    命令行 driver gcc、g++ clang、clang++、clang-cl
    语言前端 GCC 的 C、C++、Fortran 等前端 Clang、Flang 等
    优化与代码生成 GCC 自身 IR、优化 pass 与后端 LLVM IR、优化 pass 与目标后端
    链接器、库与运行时 GNU ld、libstdc++、libgcc 等 LLD、libc++、compiler-rt、libunwind 等

    这张表表达归属,不代表工具必须整套绑定。Linux 上常见的一种组合是:Clang 解析 C++,LLVM 完成主要优化和代码生成,Clang 使用集成汇编器生成目标文件,最后调用系统链接器,并链接 GNU libstdc++。

    这不是异常组合,而是工具链可组合性的正常结果。

    三、GCC 不只是一个 C 编译器

    GCC 最初的名字与 C 编译器有关,后来扩展为 GNU Compiler Collection。GCC 官方首页列出的范围包括多种语言前端与相应的库。更重要的是,GCC 自己拥有从语言前端到目标代码生成的完整内部体系。

    gcc 和 g++ 命令也首先表现为 driver。它们解析选项,根据输入文件后缀和停止选项安排预处理、编译、汇编与链接。GCC 手册明确说明:

    • -E:完成预处理后停止;
    • -S:完成编译并输出汇编后停止;
    • -c:生成目标文件,但不链接;
    • 不指定这些停止选项时,driver 通常继续链接。

    因此,“GCC 是前端,LLVM 是后端”并不是 GCC 的实际结构。GCC 有自己的前端、中间表示、优化器和目标后端。

    四、LLVM 不等于一个叫作 LLVM 的编译器命令

    LLVM 官网把 LLVM 描述为一组模块化、可复用的编译器与工具链技术,并列出多个子项目。日常对话里的“LLVM”可能指两个范围:

    1. LLVM Project:整个项目与社区,包括 Clang、LLD、libc++、compiler-rt、MLIR 等;
    2. LLVM Core:LLVM IR、分析与优化库、代码生成后端、目标描述和相关工具。

    这也是为什么“LLVM 是后端”只适合作为非常粗糙的入门口号。LLVM Core 不只有最终机器码生成;LLVM Project 的范围更不止后端。

    另外,LLVM 这个名字今天是项目名称,不应继续按早期缩写把它解释成一台传统“低级虚拟机”。LLVM IR 可以用于静态编译、JIT、分析和变换,但它不是 Java 虚拟机那种运行时产品的同义词。

    五、Clang 既是前端项目,也提供 driver

    Clang 官网给出的定位很清楚:它为 C、C++、Objective-C、OpenCL 和 CUDA 等 C 语言家族提供语言前端与工具基础设施,并服务于 LLVM Project。

    作为前端,Clang 负责:

    • 词法、语法和语义分析;
    • 建立 AST;
    • 诊断语言错误;
    • 向 LLVM IR 等低层表示降低;
    • 支撑 clangd、clang-tidy、格式化、重构和静态分析工具。

    作为用户命令,clang 与 clang++ 又是 driver。Driver 决定要运行哪些 job,把参数传给编译核心、汇编器和链接器,并选择目标平台所需的启动文件与库。

    所以“Clang 是前端”与“clang++ 会调用链接器”并不矛盾。前一句描述项目和编译阶段,后一句描述 driver 命令。

    六、使用 Clang,不代表全部组件都来自 LLVM

    下面的命令很有价值:

    clang++-21 -v -x c++ -E /dev/null -o /dev/null

    它会显示 Clang 版本、目标 triple、候选或选中的 GCC 安装,以及头文件搜索路径。在很多 Linux 发行版上,Clang 默认复用 GNU 工具链提供的 C++ 头文件和 libstdc++。

    再看链接计划:

    clang++-21 -std=c++17 -O0 -### hello.cpp -o hello

    -### 只打印计划,不执行。输出可以回答:Clang 使用集成汇编器还是外部汇编器?准备调用哪个链接器?加入了哪些启动文件和库?这些答案与发行版、安装方式和命令参数有关。

    如果明确选择 LLD,可以写:

    clang++-21 -std=c++17 -O0 -fuse-ld=lld hello.cpp -o hello-lld

    类似地,-stdlib=libc++ 和 -rtlib=compiler-rt 可以改变标准库或 compiler runtime 的选择,但前提是相应组件已经正确安装。不要把一个选项名称当成环境已经满足的证据。

    七、一个更准确的职责图

    C++ source
        │
        ▼
    clang++ driver ── plans jobs, forwards options, selects tools and libraries
        │
        ├── Clang front end ── tokens, AST, semantic checks
        │
        ├── LLVM Core ── LLVM IR, optimization, target code generation
        │
        ├── integrated assembler or external assembler ── object file
        │
        └── system linker or LLD ── executable/shared library
                               │
                               └── libstdc++ or libc++, runtimes, system libraries

    GCC 也有对应的完整路径,只是内部表示、组件边界和实现不同。它不是这张图左边缺失的部分。

    八、到底应该比较谁

    工程中常见的“GCC vs LLVM”其实不够精确。应先确认比较对象:

    • 比较 C++ 编译器实现:GCC 与 Clang/LLVM;
    • 比较链接器:GNU ld、gold、LLD 或 mold;
    • 比较 C++ 标准库:libstdc++ 与 libc++;
    • 比较 compiler runtime:libgcc 与 compiler-rt;
    • 比较调试器:GDB 与 LLDB;
    • 比较 IR 或优化框架:GIMPLE/RTL 与 LLVM IR/LLVM pass。

    再根据语言支持、目标架构、ABI、诊断、构建速度、生成代码、工具集成、许可证和维护成本做选择。

    九、常见误解

    误解一:Clang 就是 LLVM

    Clang 属于 LLVM Project,但 Clang 与 LLVM Core 不是同一个组件。

    误解二:LLVM 只有后端

    LLVM Core 包含 IR、分析、优化和代码生成;LLVM Project 还包含大量独立子项目。

    误解三:GCC 只负责前端

    GCC 是完整编译器系统,拥有自己的中间表示、优化和后端。

    误解四:使用 Clang 就一定使用 libc++ 和 LLD

    不一定。Linux 上常见默认组合是 Clang 加 GNU libstdc++ 和系统链接器。必须看 -v、-### 和实际文件。

    误解五:GCC 兼容意味着行为完全相同

    Clang driver 的重要目标是兼容 GCC 命令行和构建系统,但这不等于所有扩展、诊断、ABI 细节或未定义行为上的逐项一致。

    十、结论

    区分 GCC、Clang 和 LLVM 的最好方法不是背一句口号,而是先问当前讨论处在哪一层:项目、driver、前端、优化与代码生成、链接器,还是库与运行时。

    GCC 是完整编译器系统。LLVM 是包含核心基础设施和多个子项目的工程。Clang 是其中的 C 语言家族前端与工具基础设施,也提供用户直接调用的 compiler driver。实际工具链可以跨项目组合,最终选择必须以真实命令和环境为准。

    下一篇将使用同一个 hello.cpp,逐步生成预处理结果、汇编、目标文件和最终程序,并用四个失败例子把阶段边界真正钉牢。

    工具链可组合,为什么不等于任意二进制都兼容

    Clang能使用GNU提供的头文件与库,只说明某些组件边界有共同约定。真正的可组合性还要覆盖目标架构、对象格式、符号命名、调用约定、类型布局、异常处理和运行时依赖。任何一个名称相同,都不足以证明整条链路兼容。

    例如C++类布局或异常跨模块传播,不能只靠extern “C”解决。后者影响所声明实体的语言链接,不会把类对象布局、内存分配器和异常运行时变成统一的C契约。即便链接器找到了同名函数,调用两端对参数与对象的解释仍可能不同。

    还要区分标准库实现与ABI支持组件。libstdc++和libc++提供C++标准库;异常、栈展开与编译器辅助运算涉及其他运行时组件。Clang工具链说明将这些职责分别列出,具体组合仍需按平台核对。

    因此一份兼容声明应列明编译器、标准库、ABI/展开组件、链接器、目标及版本,说明支持哪些跨模块场景。仅有“Clang编译成功”,还没有覆盖与另一工具链产物混合、部署到另一系统或升级运行库的风险。

    怎样设计一份有意义的GCC与Clang比较

    先固定问题:比较诊断质量、编译耗时、产物大小还是运行性能?若每项都用不同源码、选项与库,就无法把差异归因于编译器实现。公平比较不是机械采用同名优化选项,而是说明语义要求、目标能力与允许的优化范围。

    一个可执行的比较记录应包含相同源码提交、输入分布、语言模式、目标CPU特性、标准库与链接器,另列各工具的完整参数。编译时间区分冷缓存、增量构建与链接阶段;运行表现区分启动、稳态和测量噪声。一个小函数的汇编好看,不足以替代应用基准。

    若目的是测试头文件选择,用-v的预处理输出即可;若目的是测试链接组合,需要实际产物与运行;若目的是性能,则必须测量。这些是不同实验,不能把其中一项成功登记为整体胜出。本文没有新增GCC/Clang性能排名。

    2026-10-18内容修订:补二进制兼容契约及可比实验设计,避免项目名比较代替机制比较。本次为文献与语义审校,未新增实验;首次发布日期、原网址及历史运行版本保持。

    适用范围与来源

    本文以 Linux、x86-64、GCC 13.3.0 与 Clang/LLVM 21.1.8 为演示环境。具体路径与默认选择不具有跨发行版保证。核心事实来源于 GCC、LLVM 与 Clang 官方文档;完整来源与原始实测日志随本文证据包保存。

    知乎独立文章:GCC、Clang、LLVM 到底是什么关系?一文理清编译器工具链


    阅读路线

    系列导航:上一篇:编译器到底是什么?一段 C++ 代码如何变成可执行程序|下一篇:预处理、编译、汇编、链接