llvm-project 上手地图:从构建到源码阅读的完整路径
2026/9/20 7:14:29 网站建设 项目流程

很多人第一次打开 llvm-project 这个仓库时,会有点懵:说好的是研究编译器,怎么刷出来一个比大多数操作系统源码还庞大的怪物?llvm-project 是 LLVM 基金会维护的 mono-repo 仓库,里面不只有编译器,还有调试器、链接器、标准库、基础库、用于写编译器的框架,甚至还有一套面向机器学习的编译器基础设施。这篇文章不打算讲某个具体 patch 的实现,而是从"拿到这个仓库之后,从哪下手、怎么理解、怎么构建、怎么用起来"这条线,把 llvm-project 的真实面貌拆开聊一遍,顺便把我在本地折腾这套东西时踩过的坑一并交代清楚。

如果你是学编译原理的学生、做性能优化的工程师、想自研语言的开发者,或者只是好奇"编译器本身是怎么造出来的",这篇内容可以当作一份索引式的上手地图。

1. 先搞清楚:llvm-project 到底是个什么项目

1.1 它不是"一个编译器",而是一条完整的工具链生产线

很多教程会顺手写一句"LLVM 是开源的编译器基础设施",但这句话等于没说。打开 llvm-project 之后你会看到十几个一级子目录,各自顶着不同的名字。这里随便列几个就能感受到这个项目的体量:

子项目一句话定位
llvm核心框架,包含 LLVM IR、优化器、CodeGen、目标后端
clangC/C++/Objective-C 前端,日常命令行用得最多的部分
clang-tools-extraclang-tidy、clangd、include-what-you-use 等附加工具
lld高性能链接器,替代系统自带的 ld
libcxx / libcxxabiC++ 标准库及 ABI 层
compiler-rt运行时库,包含 sanitizer、profile、溢出检查等底层支持
mlir面向机器学习与编译器分层的多层级 IR 框架
polly基于多面体模型的循环优化
flangFortran 前端
openmpOpenMP 运行时与编译支持
lldb调试器,功能对标 gdb

如果你只装了 clang 这个二进制,你拿到的是"编译驱动 + 前端 + 一堆库的集合"。但如果你拿到的是 llvm-project 源码,你其实拿到的是整条编译工具链的生产线:前端把高级语言翻译成 LLVM IR,优化器在 IR 上做变换,后端把 IR 变成目标平台的汇编和机器码,链接器负责把目标文件捆成可执行文件,调试器再基于这些信息提供源码级排错能力。这一整套东西,才配得上"project"这个词。

1.2 为什么它的架构会被反复抄作业

LLVM 的思路在 2000 年提出时其实很简单:把传统编译器的前后端彻底拆开。GCC 当时的前端后端是层层咬合的关系,要支持一门新语言或者一个新 CPU,代价都相当肉疼。LLVM 则定死了一套中间表示,前端的活是"把语言变成 IR",后端的活是"把 IR 变成机器码",中间这层大家共享。谁想支持新语言,就用 clang 以外的新前端来生产 IR;谁想支持新芯片,就写一个新的后端来消费 IR。

这套分层带来的收益今天已经看得很清楚了。Rust 的 rustc 一开始就直接借用 LLVM 做后端,省掉了从零开始写优化器和 CodeGen 的巨大工程量;Julia、Swift、甚至一些 GPU 的编译器也都在 LLVM 之上做文章。MLIR 出现之后,LLVM 甚至把自己的 IR 分层哲学又往下推了一层,专门服务于机器学习编译器这种需要多级抽象的场景。mlir 子目录能出现在这个仓库里,恰恰说明 LLVM 的项目边界已经不只是"编译器"三个字能概括的了。

1.3 新手最需要建立的三个认知

  • 不要把 llvm-project 当成一个待读完的代码库,它没有"读完"这种状态。理解它的方式是把链路跑通,让前端、IR、优化、后端各环节在自己的控制下走一遍。
  • 不要只盯着 llvm 目录里的 C++ 代码,clang、lld、compiler-rt 这些子项目才是你日常最常接触到的东西。
  • 不要被构建时间吓退,合理的配置和工具链能让你在一台普通机器上完成全套构建,后面第 3 节详细说。

2. 仓库结构与核心模块:从顶层看懂项目版图

2.1 一级目录:各自独立又互相依赖

llvm-project 的顶层目录基本就是子项目的边界。每个子项目内部又统一采用类似的结构,学习成本因此低了很多。比如 llvm 目录下有include/lib/tools/utils/test/等一级子目录;clang、lld、mlir 也遵循同样的组织方式。

以 llvm 核心目录为例:

路径内容备注
llvm/include/llvm公共头文件,声明所有核心接口读代码前先在这层看接口定义
llvm/lib/IRLLVM IR 的核心数据类型定义,比如ModuleFunctionBasicBlockInstruction理解了这几个类,你就理解了 IR 的骨架
llvm/lib/PassesPass 注册与调度框架,NewPM 就在这想动手写优化 pass,新版本看这个目录
llvm/lib/CodeGen后端公共流程,包括指令选择、寄存器分配、指令调度这是后端最难啃的部分
llvm/lib/Target各个 CPU 后端的实现,X86、AArch64、RISCV 等想移植新芯片就盯这里
llvm/lib/Transforms经典标量优化、向量化、IPO 等具体 pass 实现想学优化算法主要看这里
llvm/tools对外命令行工具,比如optllcllillvm-as日常调试 IR 靠它们
llvm/test回归测试集,lit驱动的测试框架提交 patch 时必须跑这一层

2.2 几个必须第一时间认识的命令行工具

构建完 llvm-project(或安装完整版 LLVM)之后,你会看到一长串llvm-开头的可执行文件。对上手理解这个项目来说,下面这几个工具优先级最高:

  • clang:C/C++ 前端入口,也是最常用的驱动。真正在终端里干活就靠它。
  • clang++:C++ 前端入口,驱动 C++ 编译。
  • opt:IR 级优化器,输入 bitcode 或 IR 文本,输出优化后的 IR。写 pass 跑测试绕不开它。
  • llc:将 LLVM IR 转为目标平台汇编或目标文件。想看后端怎么输出机器码,用它。
  • lli:直接用 JIT 方式运行 LLVM IR。想快速验证一段 IR 能跑出什么结果,用它。
  • llvm-as/llvm-dis:在文本 IR 和二进制 bitcode 之间互转。LLVM IR 有两种等价形态,这哥俩负责翻译。
  • llvm-nmllvm-objdumpllvm-readelf:替代 binutils 系列的目标文件查看工具,调试链接相关问题很顺手。
  • lld:链接器入口,调用-fuse-ld=lld即可让 clang 使用它。

我的建议是先不看源码,把这几个工具在终端里各摸一遍,用 clang 编译一段简单 C 程序,再用 clang 的-S -emit-llvm生成中间代码,用opt过一遍,最后用llc生成汇编。这条链路走通之后,你再看源码时就有一个"我知道你在这条链路的哪个环节"的大局观。

2.3 子项目之间的依赖关系与构建顺序

llvm-project 的构建系统是跨子项目统一的,使用 CMake 驱动。你不需要手动按顺序编译 clang、lld、libcxx、compiler-rt,因为总的 CMake 文件会把它们的依赖关系处理好。LLVM_PROJECTS_TO_BUILD这个参数决定你要额外编译哪些子项目。比如:

-DLLVM_PROJECTS_TO_BUILD="clang;clang-tools-extra;lld;libcxx;libcxxabi"

注意,单独编译 llvm 和在同一个构建目录里带子项目一起编,这两者之间的二进制产物目录、安装布局都有差异。我建议第一次就按"llvm + clang + lld"来配,这三个是理解工具链主流程的最佳组合。libcxx 可以等后续需要改标准库时再单独加,mlir 和 flang 对新手来说一开始不建议动。

3. 从零构建 llvm-project:一份踩过坑的配置清单

3.1 构建前的必要准备

构建 LLVM 本身对操作系统的要求不复杂,但有几个条件会直接影响成败:

条件建议值原因
内存16GB 起步,32GB 更稳链接大型 C++ 目标文件时内存峰值很高,8GB 机器配 Debug 模式很容易 OOM
磁盘至少 50GB 可用空间Release 构建产物 + 中间文件 + 源码本身,轻松吃掉 30-50GB
操作系统Linux/macOS 最顺手Windows 需要额外处理 MSVC 兼容问题,不是不行,是坑更多
CMake3.20 以上LLVM 对 CMake 版本有要求,太老直接报错
编译器自举,推荐用系统 gcc/clang 先编出一版 LLVM官方支持用宿主编译器构建,编出来的 clang 可以再编自己
Ninja建议使用比 Makefile 并行性好,增量构建快很多

开发者社区里有个默认建议:Release 模式 + Assertions 开启。这个组合既保证运行速度,又能在开发调试时帮你抓到内存越界等 bug。CMake 参数是-DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON。纯 Debug 模式编译出的 clang 慢得没法日常用,纯 Release 有时又过于"自信",把该报错的地方悄悄吞掉,所以 Release+Assertions 是最均衡的开发配置。

3.2 一个完整的构建命令组合

下面这组命令是我在 Ubuntu 22.04 上实测过多次的推荐组合:

git clone https://github.com/llvm/llvm-project.git cd llvm-project mkdir build && cd build cmake -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_ENABLE_ASSERTIONS=ON \ -DLLVM_TARGETS_TO_BUILD="X86;AArch64;RISCV" \ -DLLVM_ENABLE_PROJECTS="clang;lld" \ -DLLVM_CCACHE_BUILD=ON \ ../llvm ninja -j$(nproc)

参数逐一说一下:

  • -DLLVM_TARGETS_TO_BUILD="X86;AArch64;RISCV":默认情况下 LLVM 会为所有支持的后端生成代码,像 AMDGPU、BPF、PowerPC、SystemZ、MSP430、AVR 这些你根本用不到的 Target 也会编译出来,白白增加时间开销。按需裁剪之后,构建时间能缩短一半以上。只在本机跑的话,单独留X86就够了。
  • -DLLVM_ENABLE_PROJECTS="clang;lld":这是从 LLVM 14 开始推荐的写法,之前是-DLLVM_ENABLE_PROJECTS="clang;lld"也没错,但现在更推荐这种带目录分割的写法。它告诉构建系统:除了 llvm 核心,还需要 clang 和 lld 这两个子项目。
  • -DLLVM_CCACHE_BUILD=ON:加一层 ccache 缓存,增量编译成本大幅降低。我第一次没开,改一行代码后要重新链接一个几 GB 的 clang,中途真的会怀疑人生。

构建完成后,所有二进制都在build/bin下。验证方式很直接:

./bin/clang --version

能正常输出版本号,说明 LLVM 与 Clang 链路已经通了。

3.3 常见踩坑与对应解法

  • 内存不足导致链接崩溃:典型现象是ninja跑到 lld 链接 clang 时进程被 OOM Killer 杀掉。解法有几个方向:一是换 Release + Assertions 而不是 Debug;二是减少并行任务数-j4甚至-j2;三是用lld做宿主链接器,比系统ld快且省内存。可以这样切换:-DLLVM_USE_LINKER=lld,前提是你已经有一个可用的 lld 或系统装了 lld。
  • 磁盘被 test 目录塞满:默认构建会生成大量测试文件,如果只是先跑通链路,可以关掉测试构建:-DLLVM_INCLUDE_TESTS=OFF-DLLVM_INCLUDE_BENCHMARKS=OFF
  • CMake 版本太老:Ubuntu 20.04 自带的 CMake 3.16 编不了新版 LLVM,建议用 pip 装一个更新的,或者用 Kitware 官方 APT 源。我经历过一次整整排查了半小时,最后发现就是版本问题。
  • Windows 上 LLD 链接报错:如果你非要在 Visual Studio 工具链下构建,注意从 Windows SDK 里补齐libcmt等运行时库的链接路径。这一步对新手不友好,我更建议先在有 Linux 环境的机器上跑通整套流程。

3.4 我的实际构建体感

在一台 16 核 32GB 内存的 Linux 机器上,全量 Release+Assertions 构建 llvm+clang+lld(仅 X86 目标),首次构建大约需要 25-40 分钟,取决于 CPU 主频和磁盘 IO。开 ccache 后,第二次构建只需要几分钟。这个时间成本在编译器开发里真的很值得——因为你会反复修改、反复编译、反复跑测试,增量构建速度才是决定每天心情的关键指标。

4. LLVM IR 与 Pass 机制:理解这层"万物皆可中间表示"的设计

4.1 LLVM IR 长什么样

LLVM IR 是理解整个项目的钥匙。它既可以被当成文本文件看,也可以被编码成二进制的 bitcode,在内存中则以 C++ 对象的形式存在。三者等价,这让编译器开发者可以非常方便地在各个阶段做调试。

把下面这段 C 代码交给 clang 生成 IR:

int add(int a, int b) { return a + b; }

执行:

clang -S -emit-llvm add.c -o add.ll

你会得到一段类似这样的文本 IR:

define i32 @add(i32 noundef %a, i32 noundef %b) { entry: %add = add nsw i32 %a, %b ret i32 %add }

用生活化的方式类比:LLVM IR 就像一种"所有编译语言都能说、所有芯片都能听懂的世界语"。前端负责把 C、C++、Rust、Swift 翻译成世界语;优化器在世界语上做文章;后端再把世界语翻译成具体芯片的方言。因为大家都在同一层中间表示上工作,优化器不需要关心原始语言是啥,后端也不需要关心最终执行的高级语义,整个工程复杂度因此被拆散并降低。

4.2 Pass 是优化黑魔法的最小单位

对 IR 做变换的基本单元叫 Pass。一个 Pass 接收一个模块(通常是一整个源文件的 IR),经过分析或修改,输出一个新的模块。Pass 大致分两类:

  • 分析 Pass:只读取 IR,计算某些信息。比如统计函数调用次数、分析变量之间的关系、计算循环深度。证书这类 Pass 不会改代码,输出的是分析结果,供后续 Pass 使用。
  • 变换 Pass:修改 IR 本身。比如死代码消除、循环展开、内联等。这类 Pass 是真正干活的。

这种"小函数 + 小 Pass"的组合是 LLVM 优化器最鲜明的风格。在提交代码的 review 时,开发者通常只会把改动集中在一个很小的 Pass 里,这也是 LLVM 代码审查文化的一部分。

想手动体验 Pass 的作用,可以用opt

opt -passes=instcombine add.ll -S -o add.opt.ll

instcombine是一个经典组合变换 Pass,能把大量冗余模式化简。比如对add(a, 0)这种能优化掉的特例,它会直接把加法操作替换为a。这就是编译器优化的微观体现。跑完之后对比add.lladd.opt.ll,非常治愈。

4.3 NewPM:老 Pass 管理器与新 Pass 管理器的故事

LLVM 历史上存在过两套 Pass 框架。旧框架由legacy::PassManager承载,Pass 以继承FunctionPass等类的方式注册,接口稳定但调度机制比较僵硬。新框架叫 NewPM,引入了 PassBuilder,提供了更清晰的流水线和调试输出选项。包括opt -passes=...在内的一套工作流都基于 NewPM。

可以这么理解:旧的 Pass 管理像把一堆工人扔进车间让他们自己协调,虽然也能出货,但流程不可控;新的 Pass 管理把每个工人安排进标准流水线,可以插队、可以调位置、可以单独测试,改造起来得心应手。写新 Pass 时,如果你用新版本 LLVM,社区推荐直接基于 NewPM 开发。在代码里注册 Pass 的入口大概长这样:

llvm::PassPluginLibraryInfo getPassPluginInfo() { const auto callback = [](PassBuilder &PB) { PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "my-pass") { FPM.addPass(MyPass()); return true; } return false; }); }; return {LLVM_PLUGIN_API_VERSION, "MyPass", LLVM_VERSION_STRING, callback}; }

opt -load-pass-plugin=libMyPass.so -passes=my-pass就能把自定义 Pass 注入 pipeline。很多开发者第一次"在 LLVM 里留下自己代码"就是从写这么一个小 Pass 开始的。

4.4 从 IR 到汇编:llc 把最后一步走完

IR 优化完,后续工作是交给后端。llc这个工具会完成指令选择、寄存器分配、指令调度、汇编输出:

llc add.opt.ll -o add.s

打开add.s看看,X86 上通常就是:

addl %esi, %edi movl %edi, %eax retq

这一步可以把你的理解闭环:高级语言 -> 前端 -> IR -> 优化 -> 后端 -> 汇编 -> 链接。后端自身也是一大堆算法在支撑,尤其寄存器分配,堪称 LLVM 后端里最烧脑的部分。这里不展开,但你可以记住:后续想深入了解后端,第一站看llvm/lib/CodeGen/RegAllocFast.cpp,这个相对好读,也是一个 entry-level 的经典入口。

5. Clang 的前端到后端:一条日常高效工作流

5.1 clang 的常用姿势和 gcc 侧对比

很多人以为 clang 只是 gcc 的一个替代品,其实 clang 在设计目标上更强调模块化、可嵌入性和诊断质量。日常使用时,gcc 能做的 clang 基本都能做,而且诊断信息通常更有亲和力。下面几个命令非常常用:

# 编译并链接 clang main.c -o main # 只生成目标文件,不链接 clang -c main.c -o main.o # 生成汇编 clang -S main.c -o main.s # 生成 LLVM IR clang -S -emit-llvm main.c -o main.ll # 优化等级,O0/O1/O2/O3/Os/Oz clang -O2 main.c -o main

一个常被新开发忽略的参数是-fcolor-diagnostics(新版默认开启)。它让报错信息里不同类型的内容用不同颜色区分,比如高亮错误位置的行和列。终端下面看代码定位速度快非常多。

另一个非常有用的参数是-fdiagnostics-show-option

clang -fdiagnostics-show-option -c bad.c

它会在诊断信息末尾显示建议关闭的 warning 名称,比如-Wunused-variable,你看到之后就能用-Wno-unused-variable随手关掉这个噪音。

5.2 用 clang 观察"语言到机器"的每一跳

学习编译原理的人最大的痛点就是过程不透明。clang 的-S -emit-llvm配合llc能把不透明打开。

拿一段稍微复杂点的 C 代码举例:

int sum(int n) { int s = 0; for (int i = 0; i < n; ++i) { s += i; } return s; }

先生成 IR 看优化前后的差别:

clang -O0 -S -emit-llvm sum.c -o sum.O0.ll clang -O2 -S -emit-llvm sum.c -o sum.O2.ll

O0的 IR 里循环保留得非常直白;O2的 IR 在经过 indvars、loop-unroll、instcombine 等 pass 之后,很可能直接变成等差数列求和公式——循环整个被优化没了。这个过程放到现实中的大型代码里,就是编译器性能优化的缩略图。反复用-O0-O2对比,你对优化 pass 的直觉会建立得很快。

5.3 交叉编译与目标选项

LLVM 的设计天然适合交叉编译,因为后端和目标描述是解耦的。要在 x86 机器上给 AArch64 交叉编译:

clang --target=aarch64-linux-gnu -march=armv8-a -c foo.c -o foo.o

前提是你的构建时把 AArch64 后端包含进去(第 3 节里LLVM_TARGETS_TO_BUILD加了 AArch64 就是为了干这个)。配合lld,可以直接做全静态交叉链接:

clang --target=aarch64-linux-gnu -fuse-ld=lld foo.c -o foo.elf

这种能力在嵌入式开发里非常香。传统方案要装一整套 arm-linux-gnueabihf 工具链,而 clang 一条命令就搞定了。

5.4 用 sanitizer 在早期抓到内存问题

clang 内置一组运行时 sanitizer,能在运行期帮你发现未定义行为。最常用的三个:

Sanitizer检测对象开启方式
AddressSanitizer (ASan)堆溢出、栈溢出、use-after-free 等内存错误-fsanitize=address
UndefinedBehaviorSanitizer (UBSan)整数溢出、空指针、移位越界等未定义行为-fsanitize=undefined
ThreadSanitizer (TSan)数据竞争-fsanitize=thread

日常开发里很多人只依赖 valgrind,但 valgrind 的慢在大型项目里难以接受。ASan 在编译期插入检查代码,运行时开销通常在 1.5-3 倍之间,已经非常接近生产可用了。实测效果:

clang -fsanitize=address -g test.c -o test_asan ./test_asan

如果 test.c 里有越界访问,ASan 会打印完整的调用栈、分配栈和出错位置,比单纯段错误好排查一万倍。

6. 给想深入源码或参与贡献的人:从会用到会改

6.1 读源码的推荐路径

读 llvm-project 源码,最大的敌人是"不知道该从哪里进入"。我推荐一条相对平滑的顺序:

  1. 先读llvm/docs/里的 GettingStarted 和 LangRef。LangRef 是 LLVM IR 的语言规范,虽然长,但可以随用随查,不需要一次读完。
  2. llvm/include/llvm/IR/Module.hFunction.hBasicBlock.hInstruction.h这四个头文件的注释,把 IR 的对象模型先搭起来。它们之间是树状包含关系:Module 包含多个 Function,Function 包含多个 BasicBlock,BasicBlock 包含多条 Instruction。这条链几乎出现在所有分析和变换 pass 的入口函数里。
  3. 挑一个简单 pass 精读。比如llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp,因为instcombine的代码短小精悍,注释丰富,很适合第一次了解"这个 pass 到底怎么改 IR"。
  4. 读 clang 的 AST 那部分,从clang/lib/AST/Expr.cpp之类的小文件入手。clang 的前端旅程大致是 词法解析 -> 语法解析 -> AST -> 语义分析 -> 生成 IR。你不需要一次全搞懂,只需建立一个 map,知道哪类问题去哪个目录找。
  5. 最后如果对后端有兴趣,可以读llvm/lib/Target/X86/X86ISelLowering.cpp,这里是 X86 后端里做指令选择和 lowering 的主战场,能回答"指令怎么被选出来"这个问题。

6.2 第一次提交 Patch 的完整动作

想给 llvm-project 提交代码,第一步其实不是写代码,而是跑通流程。现在的协作流程基于 GitHub,操作和大多数开源项目类似:

  • fork llvm-project 到自己账号
  • 创建分支,改代码
  • 写测试,测试文件放在llvm/test/或对应子项目的test/目录,使用 lit + FileCheck 写回归测试
  • 本地跑ninja check-llvm check-clang验证
  • 提交 PR,等 review

如果想要初次 low-hanging fruit 的贡献方向,可以从这几个入手:

类型例子难点
修文档官网和 docs 里过时的参数说明几乎没有
补充诊断给某个 pass 加上更友好的 warning 信息需要了解对应 pass 的逻辑
补测试给新发现的 bug 加回归测试需要有 bug 场景和最小复现
修复静态分析 warning给代码里未处理的情况加显式处理需要理解上下文
翻译 LangRef 的部分章节社区一直有中文文档贡献的需求需要准确理解术语

初次建议不要直接冲击巨型新功能。LLVM 的 code review 非常严格,你花一晚上写的一个大 pass,review 后大概率被拆成十八个小 patch 再逐个讨论。参与这个社区,耐心和沟通能力跟工程能力同等重要。

6.3 一个拿上台面的学习方法

针对那些"想彻底搞懂 LLVM"的人,我建议采用"三周链路法":

  • 第一周:构建 llvm-project,用 clang/opt/llc 跑通 main -> IR -> optimized IR -> assembly 的链路,每天换着花样用命令行观察不同代码生成的不同结果。
  • 第二周:读 LLVM Kaleidoscope 教程,就是用 C++ 从零实现一门简单语言并用 LLVM 翻译成机器码的经典教程。它几乎覆盖了前端生成 IR 所需的所有核心概念,从词法分析到 JIT 全都有。
  • 第三周:挑一个自己熟悉的 C 项目,把其中几个关键文件用clang -S -emit-llvm转成 IR,再用opt做不同优化,对比 IR 变化,选一个 pass 读源码直到能解释变化原因。

这三周之后,你再看 llvm-project,就不再是"天书"而是"一座可以按地图探索的城市"。剩下的事情,无非是决定先逛哪个街区。

7. 关于学习方向的一点个人提醒

聊到最后,分享一个我自己的体会:接触 llvm-project 初期最让人焦虑的地方在于"不知道学什么才算学会"。这个项目太大了,大到几乎没有一个人能通吃所有模块。真正有价值的做法是给自己定一个小目标——是想改进某个优化 pass,还是想给某块嵌入式芯片做后端支持,还是想深入 clang 的前端诊断。目标定了,源码阅读范围自然就窄了,构建配置、测试方法、社区资料也都跟着聚焦了。

我最初就是单纯想搞清楚 clang 的告警是怎么生成的,结果一路从诊断接口追到 SourceManager,又从 SourceManager 追到 Lexer,最后对整个前端词法分析流程建立起了概念。这个过程没有走太多弯路,也没有依赖什么现成"教程",核心就是不断回答自己脑子里冒出来的"它为什么知道这一行是错的"这个问题。

所以如果你现在正对着某个编译错误或者一段晦涩源码发愁,可以换个思路:这不是障碍,这本身就是学习的第一步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询