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-format
ing 之前和之后的文件是否相同,直到空格。在 Linux/BSD/OS X 中,您可以使用 diff
和 tr
:
$ 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.c
和 2.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.h
和 stddef.h
仍然重新排序,因为它们的包含仍然是 "grouped",但是新的空行阻止 MyStruct.h
移动到标准之前库包括。
然而...
如果重新排序您的 #include
指令会破坏您的代码,您可能仍然应该执行以下操作之一:
在 header 文件中明确包含每个 header 的依赖项。在我的示例中,我需要在 MyStruct.h
.
中包含 stdint.h
在包含组之间添加注释行,明确说明排序依赖性。请记住,任何非 #include
行都应该拆分一个组,因此注释行也可以。以下代码中的注释行还阻止 clang-format
在标准库 headers.
之前包含 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 所做的更改。否则你可能会遇到隐藏的错误。
由于 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-format
ing 之前和之后的文件是否相同,直到空格。在 Linux/BSD/OS X 中,您可以使用 diff
和 tr
:
$ 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.c
和 2.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.h
和 stddef.h
仍然重新排序,因为它们的包含仍然是 "grouped",但是新的空行阻止 MyStruct.h
移动到标准之前库包括。
然而...
如果重新排序您的 #include
指令会破坏您的代码,您可能仍然应该执行以下操作之一:
在 header 文件中明确包含每个 header 的依赖项。在我的示例中,我需要在
MyStruct.h
. 中包含 在包含组之间添加注释行,明确说明排序依赖性。请记住,任何非
#include
行都应该拆分一个组,因此注释行也可以。以下代码中的注释行还阻止clang-format
在标准库 headers. 之前包含
stdint.h
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 所做的更改。否则你可能会遇到隐藏的错误。