Clang-Format 格式化代码

安装

Linux

sudo apt-get install clang-format

windows

每每到这时候就越能感受到用 Linux 作为开发环境的优势,Windows 安装就稍显复杂了。

  1. 你可以选择安装完整的 LLVM,在bin目录可以看到clang-format.exe。安装完后,将 bin 目录添加到环境变量中。

  2. 你也可以只下载clang-format.exe,从LLVM Snapshot Builds下载安装包。在下载页面的底部。同样你需要将单独下载的文件加入到环境变量中。

使用

入门使用

Linux 可以直接命令行,使用以 LLVM 代码风格格式化main.cpp, 结果直接写到main.cpp

clang g-format -i main.cpp -style=LLVM

进阶配置

如果每次编码都命令行执行一遍那也太麻烦了,而且每次修改也不止一个文件。最好的方式就是每次保存文件时自动格式化。比如 VSCode 已经内置了Clang-Format稍作配置即可实现,接下来介绍几种常见 IDE 如何配置Clang-Format

VSCode

VSCode 最常用,因为内置了Clang-Format也最容易配置。

  • 安装C/C++插件,Ctrl+Shift+X打开应用商店,搜索C/C++找到下图插件,安装后会自动安装Clang-Format程序,无需单独下载。默认安装路径为:
    C:\Users\(你的用户名)\.vscode\extensions\ms-vscode.cpptools-1.7.1\LLVM\bin\clang-format.exe
  • 打开设置页面(左下角齿轮 - 设置),搜索format,勾选Format On Save,每次保存文件时自动格式化文档。下方的设置是决定每次格式化是整个文档,还是做过修改的内容。默认是file,对整个文档进行格式化。
  • 仍在设置页面搜索Clang,配置如下。.clang-format文件最后详解。
  • 效果图

QtCreator

  • 安装Beautifier插件:帮助(Help)-关于插件(About Plugins)- Beautifier勾选,重启 QtCreator。
  • 工具(Tool)- Beautifier,配置如图。该配置,保存文档时自动格式化,并选择Clang-Format作为格式化工具。

    配置Clang-Format程序路径,如果开头已经apt install安装过,这里会自动补全。
  • Use predefined style可以选择内置的一些代码风格,如LLVMGoogle等。
  • Use customized style使用自定义的一些代码风格。点击添加(Add)将配置文件粘贴进去即可,具体配置文件见最后。
  • 别忘了点击OK保存。

Eclipse

  • 安装cppstyle插件:Help - Eclipse Marketplace - 搜索cppstyle

  • 下载cpplint
    可以去github上下载cpplint的源码,下载完之后解压放到某个目录下。

  • Window - Preferences - C/C++ - CppStyle页面把clang-format的路径添加进去,然后把cpplint的目录指向刚才下载的styleguide目录下的cpplint/cpplint.py就可以了。勾选下面的Enable cpplintRun clang-format on file save,然后点击Apply and Close保存修改并退出。如下图所示。

  • 此时再保存代码,将会出现如下错误,因为我们还未给当前项目编写配置文件.clang-format。将最后一章提到的配置文件放到当前项目的下即可,程序会自动搜索。

    Cannot find .clang-format or _clang-format configuration file under any level parent directories of path.
    Clang-format will default to Google style.

配置简介

上文多次提到了.clang-format配置文件,该文件决定了代码如何格式化,现在来介绍如何使用该文件。

导出.clang-format文件

如果重新编写一份配置文件,需要考虑的东西太多,clang-format内置了一些常见风格,我们可以根据已有的配置文件稍作修改,形成自己的代码风格。所以我们先导出一份内置的配置文件。

clang-format -style=可选格式名 -dump-config > .clang-format
# 可选格式最好写预设那那几个写最接近你想要的格式。比如我想要接近 Google C++ style 的。我就写-style=google

各个选项的含义

这里给出了配置的含义,感兴趣也可以查看官方文档,还提供了一些有案例,描述更清晰。

一些比较明显的代码分格区别

# 括号是分行,还是不分行,只有当 BreakBeforeBraces 设置为 Custom 时才有效
BraceWrapping:
  AfterCaseLabel:  true
  # class 定义后面
  AfterClass:      true
  # 控制语句后面
  AfterControlStatement: true
  AfterEnum:       true
  AfterFunction:   true
  AfterNamespace:  true
  AfterObjCDeclaration: true
  AfterStruct:     true
  AfterUnion:      true
  AfterExternBlock: true
  BeforeCatch:     true
  BeforeElse:      true
  # 缩进大括号,if else 语句后面的括号缩进
  IndentBraces:    false
  SplitEmptyFunction: true
  SplitEmptyRecord: true
  SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom

# tab 宽度
TabWidth: 4

# 换行缩进字符数
IndentWidth:     4

# 宏定义对齐
AlignConsecutiveMacros: AcrossEmptyLinesAndComments

基于LLVM代码风格修改的个人使用版本:

---
Language:        Cpp
# BasedOnStyle:  LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
# 宏定义对齐
AlignConsecutiveMacros: AcrossEmptyLinesAndComments
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
AlignConsecutiveBitFields: true
AlignEscapedNewlines: Right
AlignOperands:   true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
# 是否允许短方法单行,只有一行的函数将不会分行,直接写在函数名后
AllowShortFunctionsOnASingleLine: false
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: true
# 括号是分行,还是不分行,只有当 BreakBeforeBraces 设置为 Custom 时才有效
BraceWrapping:
  AfterCaseLabel:  true
  # class 定义后面
  AfterClass:      true
  # 控制语句后面
  AfterControlStatement: true
  AfterEnum:       true
  AfterFunction:   true
  AfterNamespace:  true
  AfterObjCDeclaration: true
  AfterStruct:     true
  AfterUnion:      true
  AfterExternBlock: false
  BeforeCatch:     true
  BeforeElse:      true
  # 缩进大括号
  IndentBraces:    false
  SplitEmptyFunction: true
  SplitEmptyRecord: true
  SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit:     100
CommentPragmas:  '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat:   false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
  - foreach
  - Q_FOREACH
  - BOOST_FOREACH
IncludeBlocks:   Preserve
IncludeCategories:
  - Regex:           '^"(llvm|llvm-c|clang|clang-c)/'
    Priority:        2
    SortPriority:    0
  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
    Priority:        3
    SortPriority:    0
  - Regex:           '.*'
    Priority:        1
    SortPriority:    0
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: false
IndentGotoLabels: true
IndentPPDirectives: None
# 换行缩进字符数
IndentWidth:     4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd:   ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 0
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments:  true
SortIncludes:    true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles:  false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard:        Latest
StatementMacros:
  - Q_UNUSED
  - QT_REQUIRE_VERSION
TabWidth:        8
UseCRLF:         false
UseTab:          Never
...

格式化最新的commit代码

clang-format还提供一个clang-format-diff.py脚本,用来格式化patchcode review提交代码前,跑一遍下面的代码。

// 格式化最新的 commit,并直接在原文件上修改
git diff -U0 HEAD^ | clang-format-diff.py -i -p1

常见问题

如何看懂官方文档并编写配置文件

官方文档里有各种设置的示例代码,即使不知道想要的格式化是哪个配置参数,翻一翻官方文档是示例大概率能找到。那么找到了想要的配置参数,如何在文件里配置呢?

以宏定义对齐为例。我们想要宏定义的值保持对齐的状态,如下一节图片所示。可以翻一遍官方文档,可以发现这个示例代码对应的参数可能是我们想要的,AlignConsecutiveMacros翻译为对齐连续的宏定义。那应该八九不离十了。

找到了参数如何编写配置文件呢?可以继续看这个参数下面的更多示例,每一个示例都对应一个配置可能值Possible values

  • ACS_None (in configuration: None)
    Do not align macro definitions on consecutive lines.
    ACS_None为这个配置的缩写,None表示在配置文件里的值。该配置表示不对宏定义进行对齐操作,在配置文件里可以添加如下:

    AlignConsecutiveMacros: None
  • ACS_Consecutive (in configuration: Consecutive)
    Align macro definitions on consecutive lines. This will result in formattings like:

    #define SHORT_NAME       42
    #define LONGER_NAME      0x007f
    #define EVEN_LONGER_NAME (2)
    
    #define foo(x) (x * x)
    /* some comment */
    #define bar(y, z) (y + z)

    ACS_Consecutive为这个配置的缩写,Consecutive表示在配置文件里的值。该配置表示对连续的宏定义进行对齐,在配置文件里可以添加如下:

    AlignConsecutiveMacros: Consecutive

宏定义对齐失效

# 宏定义对齐
AlignConsecutiveMacros: AcrossEmptyLinesAndComments

使用宏定义对齐更详细的配置,可以参考官方文档。使用该配置一定要使用等宽的字体,否则配置生效但是显示不正确。
比如我是用微软雅黑字体作为编码字体,因为该字体每个字符不等宽,导致格式化正确,但是显示不正确。

如果将字体换位等宽字体如常用的Consolas,就可以正常显示。