现代编译器通常采用“三段式”架构,分为:前端(Frontend)、中端(Optimizer,优化器)和后端(Backend,代码生成器)。前端的任务是把程序员编写的、具备特定语法的高级语言源代码(如 C++、Java、Python),翻译成一种与具体硬件平台无关的中间表示(IR,Intermediate Representation),同时检查代码中的语法和语义错误。

clangd

clangd 是一个基于 Clang 的 C/C++ 语言服务器。它负责给 VS Code、Vim、CLion 等编辑器提供:代码跳转、自动补全、查找引用、实时语法检查、重命名、查看类型、函数签名、参数提示

compile_commands.json

[
  {
    "directory": "/home/plh/work/nvr/build",
    "command": "/opt/toolchain/bin/arm-linux-g++ -std=c++11 -DPLATFORM_A1 -I../modules/base/include -I../modules/media/include -c ../modules/decode/DecoderManager.cpp",
    "file": "../modules/decode/DecoderManager.cpp"
  }
]
  • file: 这是哪个源文件的编译命令

  • directory: 编译命令在哪个工作目录执行。相对路径需要以这个目录为基准解析。

  • **command:**表示完整编译命令

    • 编译器
    • -I 头文件路径
    • -D 宏定义
    • -std=c++11
    • 平台参数
    • 目标架构参数

如何实现代码跳转

假设有以下代码:

DecoderManager::instance().addSlot(channel, streamType);

当你点击 addSlot 跳转时,clangd 大致做了以下工作

  1. 找到这个 .cpp 的编译命令:clangd 从 compile_commands.json 中找到对应记录:

    {
      "file": "DecoderManager.cpp",
      "command": "g++ -I... -D... -std=c++11 ..."
    }
    
  2. 按照真实参数解析源码

使用Clang前端处理:

#include "DecoderManager.h"
  1. 建立 AST

clangd 会将代码构建成 AST(Abstract Syntax Tree,抽象语法树)。

CallExpr
├── MemberExpr: DecoderManager::addSlot
├── DeclRefExpr: channel
└── DeclRefExpr: type

因此 clangd 知道这里调用的是:

DecoderManager::addSlot

而不是另一个类中同名的 addSlot

  1. 建立符号索引

clangd 会给项目中的符号建立索引,例如:

DecoderManager
DecoderManager::addSlot
DecoderManager::removeSlot
ChannelSource::subscribe
StreamHub::start

Clang 编译器

Clang 前端

负责理解 C、C++、Objective-C 代码,包括:

  • 处理 #include
  • 处理 #define
  • 识别关键字和标识符
  • 检查语法
  • 检查类型
  • 解析模板
  • 解析函数重载
  • 建立 AST

例如:

Foo* p = new Bar;

前端会检查:

  • Foo 是否是合法类型
  • Bar 是否已定义
  • Bar* 能否转换为 Foo*
  • 是否存在继承关系
  • 构造函数能否调用

如果不合法,前端直接报错。