C/C++ 代码风格自动化:clang-format 与 clang-tidy 实战

为什么你的 C/C++ 代码看起来总是不一致?

团队合作中,你是否经常因为缩进、大括号位置、命名风格(驼峰还是下划线?)而和同事争论?手动调整不仅低效,还容易在 git blame 中留下大量毫无意义的空白变更。

本文介绍一套零人工干预的解决方案:用 clang-format 统一格式,用 clang-tidy 检查和修正命名,并通过 Git Hook 与 CI 流水线强制执行,让所有代码像出自一人之手。

第一步:用 clang-format 自动格式化

clang-format 是 LLVM 家族中的瑞士军刀,它支持 LLVM、Google、Chromium、Mozilla 等预定义风格,也能通过 .clang-format 文件深度定制。

生成项目级配置文件

在项目根目录运行下面命令,生成默认 LLVM 风格的完整配置文件,再按需修改:

clang-format -style=LLVM -dump-config > .clang-format

一个实用的 .clang-format 示例(基于 LLVM,缩进改为 4 空格):

BasedOnStyle: LLVM
IndentWidth: 4
PointerAlignment: Left
AllowShortFunctionsOnASingleLine: None
ColumnLimit: 120
...

将这个文件纳入版本控制,所有人使用相同的标准,从此告别“缩进战争”。

注意:有了 .clang-format 文件并不代表系统会自动格式化,你需要通过编辑器插件、手动命令或下面介绍的自动化流程来触发它。

第二步:用 clang-tidy 统一命名规范

clang-format 只管“形”(空白、换行、括号),不管“名”(变量叫 my_var 还是 myVar)。这个时候就该 clang-tidy 出场了。

它的 readability-identifier-naming 检查器不仅能检测命名违规,还能自动修复

命名检查配置 (.clang-tidy)

Checks: 'readability-identifier-naming'
CheckOptions:
  - key: readability-identifier-naming.FunctionCase
    value: camelBack          # 函数名用小驼峰
  - key: readability-identifier-naming.VariableCase
    value: lower_case        # 变量用下划线小写
  - key: readability-identifier-naming.ClassCase
    value: CamelCase         # 类名用大驼峰
  - key: readability-identifier-naming.MacroDefinitionCase
    value: UPPER_CASE        # 宏定义全大写

手动运行指南:单文件 & 整个项目

在实际使用中,我们经常需要手动格式化一个文件,或者一次性修复整个项目。下面列出常用命令,假设你已经配置好 .clang-format.clang-tidy

clang-format 手动命令

1. 格式化单个文件

# 直接修改文件
clang-format -i main.cpp

# 仅预览效果(不修改文件)
clang-format main.cpp

2. 格式化整个项目

# Linux / macOS(递归查找所有 .c .cpp .h .hpp)
find src/ -type f \( -name "*.c" -o -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) -exec clang-format -i {} +

# Windows PowerShell
Get-ChildItem -Recurse -Include *.c, *.cpp, *.h, *.hpp | ForEach-Object { clang-format -i $_.FullName }

clang-tidy 手动命令

clang-tidy 需要知道编译选项(如头文件路径),命令末尾的 -- 之后可以传递编译参数,也可以使用 compile_commands.json

1. 检查/修复单个文件

# 仅检查(不修改文件)
clang-tidy main.cpp -- -I./include

# 自动修复(修改文件,建议先备份)
clang-tidy -fix main.cpp -- -I./include

如果项目有 compile_commands.json(通常由 CMake 生成),直接运行:

clang-tidy -fix main.cpp

2. 对整个项目运行 clang-tidy

批量处理时推荐使用 run-clang-tidy 脚本(一般随 clang-tidy 安装),它可以并行处理多个文件,并自动应用 -fix

# 对所有源文件运行检查(利用 compile_commands.json)
run-clang-tidy -p build/ -fix

# 只检查特定模式的文件
run-clang-tidy -p build/ -fix -glob='src/**/*.cpp'

如果你没有 run-clang-tidy,也可以用 findxargs 循环处理,但注意 clang-tidy 本身不支持一次性处理多个文件,需要逐个执行:

# Linux / macOS 串行处理(较慢)
find src/ -name "*.cpp" -exec clang-tidy -fix {} -- -I./include \;

注意事项

  • clang-format -i 会直接修改文件,建议在版本控制下使用,修改后可 git diff 确认。
  • clang-tidy -fix 的修改可能引入逻辑错误(尽管概率较低),重命名后务必编译测试。
  • 务必先将 .clang-format.clang-tidy 提交到仓库,以保证团队环境一致。

第三步:强制执行——让规范自动落地

光有工具还不够,必须把它们嵌入到开发流程中,才能真正做到“零漏网之鱼”。这里介绍两种主流方式:本地 Git HookCI 流水线

方式一:本地 Git pre-commit Hook

在每次 git commit 时自动检查本次修改的文件,格式不符则拒绝提交。

1. 创建 Hook 脚本

在项目根目录下,创建 .git/hooks/pre-commit 文件(无后缀名),并赋予执行权限:

touch .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

粘贴以下脚本:

#!/bin/bash

# 只检查暂存区中 C/C++/头文件
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(c|cpp|h|hpp)$')

if [ -z "$STAGED_FILES" ]; then
    exit 0
fi

echo "正在检查代码格式..."

for FILE in $STAGED_FILES; do
    clang-format --dry-run --Werror "$FILE"
    if [ $? -ne 0 ]; then
        echo "❌ 文件 $FILE 格式不符合 .clang-format,已阻止提交。"
        echo "💡 请运行: clang-format -i $FILE"
        exit 1
    fi
done

echo "✅ 所有文件格式检查通过。"
exit 0

现在,每当你提交修改过的 C/C++ 文件,这个脚本都会自动运行检查。

2. 团队共享方案

由于 .git/hooks 目录无法被 Git 跟踪,你需要一种方法让团队其他成员也能启用这个 hook。推荐两种做法:

简单做法:将 pre-commit 脚本放在项目目录(如 scripts/pre-commit),在 README 中指引成员复制到 .git/hooks/

推荐做法:使用 pre-commit 框架(https://pre-commit.com)。在项目根目录创建 .pre-commit-config.yaml

repos:
  - repo: https://github.com/pre-commit/mirrors-clang-format
    rev: v17.0.6   # 请替换为你安装的版本
    hooks:
      - id: clang-format
        files: \.(c|cpp|h|hpp)$

每个成员只需在克隆仓库后执行一次:

pip install pre-commit   # 或者通过 brew / apt 安装
pre-commit install

此后每次 git commit 都会自动调用 clang-format 检查。

方式二:CI 流水线(GitHub Actions 示例)

作为最终防线,即使有人绕过了本地 hook(例如使用 --no-verify),CI 也能在代码推送到远程时进行检查。

在项目 .github/workflows/ 目录下创建 clang-format-check.yml

name: Clang-Format Check

on: [push, pull_request]

jobs:
  format-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install clang-format
        run: sudo apt-get install -y clang-format

      - name: Check code format
        run: |
          # 查找所有 C/C++/头文件,排除 build 等目录
          find . -type f \( -name '*.c' -o -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) \
            -not -path './build/*' \
            -print0 | xargs -0 clang-format --dry-run --Werror

这样,任何人推送代码或创建 Pull Request 时,GitHub Actions 都会自动检查格式。如果不符合,流水线失败,代码无法合并。

你还可以为 GitLab CI、Jenkins 等其他平台配置类似逻辑,原理完全相同。

三步走,告别代码风格烦恼

工具 作用 触发方式
.clang-format 定义格式规范(缩进、换行等) 编辑器保存 / 手动命令
.clang-tidy 检查命名、潜在 bug 手动运行 / CI
pre-commit Hook 提交前自动检查格式 本地 git commit
CI 流水线 远程强制检查,确保合入标准 Push / Pull Request

从此,代码风格不再需要人肉审查,团队精力可以完全聚焦在逻辑本身。现在,就在你的项目中试试吧!