clang-format 可以破坏我的代码吗?

Can clang-format break my code?

由于 clang-format 是一种仅用于重新格式化代码的工具,这种格式化是否有可能破坏工作代码或至少改变其工作方式?是否存在某种合同,它 will/can 不会改变代码的工作方式?

我们有很多代码要用 clang-format 格式化。这意味着,许多代码行将发生变化。不必检查仅因 clang-format 而更改的每一行代码将大大简化此过程。

我会说 clang-format 不会改变代码的工作方式。另一方面,如果可以保证这一点,我也不是 100% 确定。

我想它不会,因为它是建立在 clang 的静态分析之上的,因此了解代码本身的结构,而不仅仅是一个仅对文本进行操作的愚蠢的源代码格式化程序(其中一个能够使用编译器库的好处)。鉴于格式化程序使用与编译器本身相同的解析器和词法分析器,我会感到足够安全,因为它不会吐出与您提供的代码行为相同的代码。

您可以在此处查看 C++ 格式化程序的源代码:http://clang.llvm.org/doxygen/Format_8cpp_source.html

由于 clang-format 只影响空白字符,您可以检查 clang-formating 之前和之后的文件是否相同,直到空格。在 Linux/BSD/OS X 中,您可以使用 difftr

$ diff --ignore-all-space <(tr '\n' ' ' < 2.c ) <(tr '\n' ' ' < 1.c)

1.c:

#include <stdio.h>
int main() {printf("Hello, world!\n"); return 0;}

2.c:

#include <stdio.h>
int main() {
    printf("Hello, world!\n");
    return 0;
}

diff 命令的输出为空,这意味着文件 1.c2.c 除了空格之外是相同的。

正如 Karoly 在他的评论中提到的,请注意,在理想情况下,您仍然需要检查重要的空间,例如字符串文字。但在现实世界中,我相信这个测试绰绰有余。

它肯定会改变您的代码的工作方式。原因是 C 程序可以查看其源代码的某些属性。我正在考虑的是 __LINE__ 宏,但我不确定是否有其他方法。

考虑 1.c:

#include <stdio.h>
int main(){printf("%d\n", __LINE__);}

然后:

> clang 1.c -o 1.exe & 1.exe
2

现在做一些clang-format:

> clang-format -style=Chromium 1.c >2.c

2.c是:

#include <stdio.h>
int main() {
  printf("%d\n", __LINE__);
}

当然,输出也发生了变化:

> clang 2.c -o 2.exe & 2.exe
3

简短回答:是。


clang-format 工具有一个 -sort-includes 选项。更改 #include 指令的顺序肯定会改变现有代码的行为,并且 可能会破坏现有代码。

由于相应的 SortIncludes 选项被几个 built-in 样式设置为 true,因此 clang-format 将重新排序您的包含可能并不明显.

MyStruct.h:

struct MyStruct {
    uint8_t value;
};

original.c:

#include <stdint.h>
#include <stddef.h>
#include "MyStruct.h"

int main (int argc, char **argv) {
    struct MyStruct s = { 0 };
    return s.value;
}

现在假设我们 运行 clang-format -style=llvm original.c > restyled.c.

restyled.c:

#include "MyStruct.h"
#include <stddef.h>
#include <stdint.h>

int main(int argc, char **argv) {
  struct MyStruct s = {0};
  return s.value;
}

由于 header 文件的重新排序,我在编译 restyled.c 时遇到以下错误:

In file included from restyled.c:1:
./MyStruct.h:2:5: error: unknown type name 'uint8_t'
    uint8_t value;
    ^
1 error generated.

不过,这个问题应该很容易解决。你不太可能有像这样的 order-dependent 包含,但如果有,你可以通过在需要特定顺序的 header 组之间放置一个空行来解决问题,因为显然 clang-format 仅对 #include 指令组进行排序,中间没有非 #include 行。

fixed-original.c:

#include <stdint.h>
#include <stddef.h>

#include "MyStruct.h"

int main (int argc, char **argv) {
    struct MyStruct s = { 0 };
    return s.value;
}

fixed-restyled.c:

#include <stddef.h>
#include <stdint.h>

#include "MyStruct.h"

int main(int argc, char **argv) {
  struct MyStruct s = {0};
  return s.value;
}

请注意,stdint.hstddef.h 仍然重新排序,因为它们的包含仍然是 "grouped",但是新的空行阻止 MyStruct.h 移动到标准之前库包括。


然而...

如果重新排序您的 #include 指令会破坏您的代码,您可能仍然应该执行以下操作之一:

  1. 在 header 文件中明确包含每个 header 的依赖项。在我的示例中,我需要在 MyStruct.h.

  2. 中包含 stdint.h
  3. 在包含组之间添加注释行,明确说明排序依赖性。请记住,任何非 #include 行都应该拆分一个组,因此注释行也可以。以下代码中的注释行还阻止 clang-format 在标准库 headers.

  4. 之前包含 MyStruct.h

alternate-original.c:

#include <stdint.h>
#include <stddef.h>
// must come after stdint.h
#include "MyStruct.h"

int main (int argc, char **argv) {
    struct MyStruct s = { 0 };
    return s.value;
}

clang-format 在项目中重新格式化了 ASM 代码,因为我们有效地做到了这一点:

#define ASM _asm

ASM {

  ...

}

不会中断工作流程

系统有配置开关: "C_Cpp.clang_format_sortIncludes": 假, 但它不起作用,我不知道出了什么问题...

我的版本is:ms-vscode.cpptools-0.13.1

这是我的解决方案:

为了稳定的工作流程,使用语法:

// clang 格式关闭

...这是您的代码

// clang-格式开启

如果您在代码和格式化设置中使用特殊结构,它可能会破坏您的代码。

内联汇编程序

如果您通常使用 gcc 编译代码并使用 gcc 风格的内联汇编器,clang 格式很可能会破坏寄存器变量的命名,因为它将 % 字符视为运算符。

asm_movq(%[val2], %%mm0)

将重新格式化为

asm_movq(% [val2], % % mm0)

将不再编译。

在宏中构建路径

如果您在不使用字符串的情况下使用宏构建路径,clang-format 将再次将“/”字符视为运算符并在其周围放置空格。

提升例如使用这样的构造:

#   define AUX778076_PREPROCESSED_HEADER \
    BOOST_MPL_CFG_COMPILER_DIR/BOOST_MPL_PREPROCESSED_HEADER

构建头文件的路径。 '/' 在这里不是运算符,但由于它不在字符串中,因此 clang-format 将其视为运算符并在其周围放置空格,从而创建不同的路径。 头文件的include显然会失败

结论

是的,clang-format 可以破坏你的代码。如果您使用的是边缘情况或语言标准之外的非常具体的构造,或者只是您非常具体的编译器(不是 clang)的扩展,那么您将需要检查 clang-format 所做的更改。否则你可能会遇到隐藏的错误。