我遇到了一些涉及Clang -Wconditional-uninitialized
的异常行为。请考虑以下示例:
typedef struct { int x[1]; } test_t;
test_t foo(void);
test_t foo() {
test_t t;
for(int i = 0; i < 1; i++) t.x[i] = 0;
return t;
}
int main() { }
用例如编译
clang -o test -Weverything test.c
发出以下警告:
test.c:6:12: warning: variable 't' may be uninitialized when used here [-Wconditional-uninitialized]
return t;
^
test.c:4:5: note: variable 't' is declared here
test_t t;
^
1 warning generated.
但是,换行:
for(int i = 0; i < 1; i++) t.x[i] = 0;
同
t.x[0] = 0;
没有警告。请注意,使用-Wconditional-uninitialized
就足够了,并且没有必要通过-Weverything
。来自两种情况的-O3
优化组件是相同的,在两种情况下都发射单个xorl %eax, %eax
。这是我可以创建的显示此行为的最小示例。
我把一些Clang的-Weverything
输出带了一粒盐,但这对我来说就像一个bug而且我很困惑。是否存在某种未定义的行为,我正在调用它导致这种情况,或者这是一种误报?保持-Wconditional-uninitialized
启用通常是一个好主意还是坏主意?我在Linux上的Clang 3.8.1和macOS上的(Apple)9.1.0上看到了相同的行为。
正如@R ..在评论中建议的那样,我已将此报告为LLVM bug 38856。显然,据他们说:
-Wconditional-uninitialized预计会出现误报:如果简单的控制流分析无法证明变量是在每次使用之前写入的,则会发出警告。它不是字段敏感的,也不是数据流敏感的,所以它不能区分写入一个数组元素和写入整个数组之间的区别,并且不知道循环实际上总是至少执行一次。
相比之下,只有在我们可以证明你的程序有错误(或包含死代码)时才会发出警告。
我们可以在这里做一些改进来降低误报率(一些粗略的场敏感度可能相对简单地添加),但我们不想在这里重新创建铿锵静态分析仪的全部智能,并保持这个警告很简单,我们可以将它作为正常编译的一部分运行是一个目标。
我还在该报告中指出,即使其中一个数组成员显然未初始化,以下示例也不会发出警告:
#include <stdio.h>
typedef struct { int x[2]; } test_t;
test_t foo_3(void);
test_t foo_3() {
test_t t;
t.x[0] = 0;
return t;
}
int main() {
test_t t;
t = foo_3();
printf("%i %i\n", t.x[0], t.x[1]);
}
哪位得到了我的答复:
正如我在评论#1中提到的,我们当前执行的分析无法区分写入一个数组元素和写入整个数组。在简单的情况下,这应该是相对简单的改进,所以我认为由于这个原因这个错误是有效的(但是低优先级,因为这个警告预计有误报)。
所以-Wconditional-uninitialized
因出现误报而闻名。显然,静态分析仪并不像我预期的那样彻底。我感到惊讶的是,在给出更复杂的分析器似乎工作得更好的情况之前我没有遇到过这样的事情,但是@toohonestforthissite也在上面的评论中指出,通过像这样的结构传递数值不是一个在C中要做的标准事情。
-Wno-conditional-uninitialized
或#pragma clang diagnostic ignored "-Wconditional-uninitialized"
应该对那些想要在这种情况下使用-Weverything
的人保持警告。