# compile_commands.json 简易科普与编译选项解释

> 解释编译数据库的 directory、file、arguments 与常见选项，给出 CMake 生成命令和 clangd 排障入口。

作者：[汤国放](https://tonwork.fun/#person)
发布日期：2024-07-05
更新日期：2026-10-08
原文：https://tonwork.fun/blog/compile_commands-json-%E7%AE%80%E6%98%93%E7%A7%91%E6%99%AE%E4%B8%8E%E7%BC%96%E8%AF%91%E9%80%89%E9%A1%B9%E8%A7%A3%E9%87%8A/

---

`compile_commands.json` 记录每个翻译单元实际使用的编译命令。clangd、clang-tidy 需要它，才能知道源码对应的语言标准、宏、头文件目录和目标平台。一个文件可以有多条配置，数据库不是“全项目统一一组参数”。[Clang 格式规范](https://clang.llvm.org/docs/JSONCompilationDatabase.html)

## 看懂一条真实形状的记录

下面的 `/work/demo` 是示例工程路径，使用时换成自己的绝对路径：

```json
[
  {
    "directory": "/work/demo/build",
    "file": "/work/demo/src/main.cpp",
    "arguments": [
      "/usr/bin/clang++",
      "-std=c++17",
      "-I/work/demo/include",
      "-DAPP_DEBUG=1",
      "-Wall",
      "-Wextra",
      "-c",
      "/work/demo/src/main.cpp",
      "-o",
      "main.cpp.o"
    ],
    "output": "main.cpp.o"
  }
]
```

`directory` 是编译时工作目录，相对路径都以它为基准；`file` 是这条命令对应的源文件；`arguments` 是已经拆好的参数数组，首项是编译器。`output` 可选，用来区分同一输入的不同编译输出。也可以用一个 `command` 字符串代替 `arguments`，但数组省掉了 shell 引号转义，Clang 官方更推荐它。[字段定义](https://clang.llvm.org/docs/JSONCompilationDatabase.html#format)

数据库通常由构建系统生成，长期手改很容易被下次配置覆盖。头文件多数没有独立记录，clangd 会参考包含它的源文件或推断编译命令。

## CMake 工程怎么生成

如果项目已经有 `CMakeLists.txt`，用 Ninja 生成器配置并构建：

```sh
cmake -S . -B build -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build
```

第一步生成 `build/compile_commands.json`；第二步让生成头文件等构建产物实际存在。这个开关适用于 Makefile 和 Ninja 生成器，不能套用到所有 IDE 生成器，Unity Build 也有额外限制。[CMake 官方说明](https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html)

CMake 生成方式按官方文档说明；下面的 clangd 检查使用手工最小数据库验证，不涵盖完整 CMake 或 Bazel 工程。Bazel 项目的路径不同，可以看 [Bazel 编译数据库笔记](https://tonwork.fun/blog/bazel-%E7%94%9F%E6%88%90-compile_commands-json-%E7%AE%80%E6%98%93%E7%89%88%E6%95%99%E7%A8%8B/)。

## 常见选项，哪些会影响补全

| 选项 | 作用 | 排障时关注什么 |
| --- | --- | --- |
| `-I/path` | 添加普通头文件搜索目录 | 相对目录是否基于 `directory` 存在 |
| `-isystem /path` | 添加系统头文件目录 | 系统头文件诊断处理与普通头文件不同 |
| `-iquote /path` | 为 `#include "..."` 添加目录 | 不参与尖括号头文件的这部分搜索 |
| `-DAPP_DEBUG=1` | 定义宏 | 编辑器与构建是否进入同一条件分支 |
| `-UAPP_DEBUG` | 取消宏定义 | 参数顺序是否把之前定义取消了 |
| `-std=c++17` | 指定语言标准 | 新语法是否因错误标准被标红 |
| `--target=...` / `--sysroot=...` | 指定目标和工具链根目录 | 交叉编译头文件是否匹配目标平台 |
| `-Wall -Wextra` / `-Werror` | 启用警告 / 将警告视为错误 | 严格警告有助于检查，但不保证程序没有缺陷 |

另外，`-fstack-protector` 会加入运行时栈保护代码，不能把它和 `-Werror` 合起来理解成“编译期能检出栈溢出”。`-fno-omit-frame-pointer` 用于保留帧指针、辅助栈回溯，也不等于所有配置下调用栈都完整。选项的具体可用性需要按 GCC/Clang 和目标平台核对。[GCC 预处理选项](https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html)、[GCC 插桩选项](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html)

## 补全报错时先看编译命令

```sh
clangd --check=src/main.cpp --compile-commands-dir=build --log=verbose
```

日志出现 `Compile command from CDB`，说明选中了数据库记录；如果是 `Generic fallback command`，先修数据库位置或源文件路径，再查头文件。`--compile-commands-dir` 指向目录，不是 JSON 文件自身。[clangd 排障说明](https://clangd.llvm.org/guides/system-headers#you-can-build-your-project-but-clangd-is-complaining-about-missing-headers)

我排这类问题通常只看三件事：选没选中正确记录，参数中的路径存不存在，命令能不能在该工作目录单独编译。详细编辑器配置放在 [clangd 与 VS Code 入门](https://tonwork.fun/blog/%E4%BB%A3%E7%A0%81%E8%87%AA%E5%8A%A8%E6%8F%90%E7%A4%BA-clangd-vscode%E5%85%A5%E9%97%A8/)，完整阅读顺序见 [C++ 与工具链导读](https://tonwork.fun/topics/cpp-toolchain/)。
