我尝试过的所有C编译器都未能在下面的代码段中检测到未初始化的变量。但这种情况在这里是显而易见的。
不要担心此代码段的功能。这不是真正的代码,我把它剥离了以便调查这个问题。
BOOL NearEqual (int tauxprecis, int max, int value)
{
int tauxtrouve; // Not initialized at this point
int totaldiff; // Not initialized at this point
for (int i = 0; i < max; i++)
{
if (2 < totaldiff) // At this point totaldiff is not initialized
{
totaldiff = 2;
tauxtrouve = value; // Commenting this line out will produce warning
}
}
return tauxtrouve == tauxprecis ; // At this point tauxtrouve is potentially
// not initialized.
}
另一方面,如果我发表评论tauxtrouve = value ;
,我会收到"local variable 'tauxtrouve' used without having been initialized"
警告。
我试过这些编译器:
答案 0 :(得分:65)
未初始化此变量的显而易见性被夸大了。路径分析需要花费时间,而您的编译器供应商要么不想实现该功能,要么认为它会花费您太多时间 - 或者您只是没有明确选择加入。
例如,使用clang
:
$ clang -Wall -Wextra -c obvious.c
$ clang -Wall -Wextra --analyze -c obvious.c
obvious.c:9:11: warning: The right operand of '<' is a garbage value
if (2 < totaldiff) // at this point totaldiff is not initialized
^ ~~~~~~~~~
obvious.c:16:21: warning: The left operand of '==' is a garbage value
return tauxtrouve == tauxprecis ; // at this point tauxtrouve is potentially
~~~~~~~~~~ ^
2 warnings generated.
这些天真例子的执行时间差异可以忽略不计。但想象一下翻译单元有数千行,数十个函数,每个函数都有循环和重嵌套。路径的数量很快就会变得很复杂,并且成为分析循环的第一次迭代是否在该比较之前进行分配的重要负担。
编辑:@Matthieu指出,对于LLVM / clang,由于IR使用的SSA表示法,找到未初始化使用值所需的路径分析不会随着嵌套的增加而复合。
它并不像我希望的那样简单到“-S -emit-llvm
”,但我找到了他描述的SSA符号输出。老实说,我对LLVM IR的熟悉程度不够,但我会用Matthieu的话说。
底线:将clang
与--analyze
一起使用,或诱使某人修复gcc
错误。
; Function Attrs: nounwind uwtable
define i32 @NearEqual(i32 %tauxprecis, i32 %max, i32 %value) #0 {
br label %1
; <label>:1 ; preds = %7, %0
%tauxtrouve.0 = phi i32 [ undef, %0 ], [ %tauxtrouve.1, %7 ]
%i.0 = phi i32 [ 0, %0 ], [ %8, %7 ]
%2 = icmp slt i32 %i.0, %max
br i1 %2, label %3, label %9
; <label>:3 ; preds = %1
%4 = icmp slt i32 2, 2
br i1 %4, label %5, label %6
; <label>:5 ; preds = %3
br label %6
; <label>:6 ; preds = %5, %3
%tauxtrouve.1 = phi i32 [ %value, %5 ], [ %tauxtrouve.0, %3 ]
br label %7
; <label>:7 ; preds = %6
%8 = add nsw i32 %i.0, 1
br label %1
; <label>:9 ; preds = %1
%10 = icmp eq i32 %tauxtrouve.0, %tauxprecis
%11 = zext i1 %10 to i32
ret i32 %11
}
答案 1 :(得分:50)
是的,它应该对该未初始化的变量发出警告,但它是a GCC bug。给出的例子是:
unsigned bmp_iter_set ();
int something (void);
void bitmap_print_value_set (void)
{
unsigned first;
for (; bmp_iter_set (); )
{
if (!first)
something ();
first = 0;
}
}
并被诊断为-O2 -W -Wall
。
不幸的是,今年是这个错误发生10周年!
答案 2 :(得分:11)
这个答案仅针对GCC。
经过进一步的调查和评论后,还有比我以前的答案更多的事情。此代码段有两个未初始化的变量,并且由于其他原因,每个变量都未被检测到。
首先,-Wuninitialized
选项的GCC documentation表示:
由于这些警告取决于优化,因此存在警告的确切变量或元素取决于所使用的GCC的精确优化选项和版本。
以前版本的GCC手册更明确地说明了这一点。这是[{3}}:
的摘录这些警告仅在优化编译时才有可能,因为它们需要仅在优化时计算的数据流信息。如果你没有指定-O,你根本就不会得到这些警告。
似乎当前版本可能会在没有-O
的情况下发出没有未初始化变量的警告,但您仍可以获得更好的结果。
如果我使用gcc -std=c99 -Wall -O
编译您的示例,我会得到:
foo.c: In function ‘NearEqual’:
foo.c:15:21: warning: ‘tauxtrouve’ is used uninitialized in this function [-Wuninitialized]
return tauxtrouve == tauxprecis ; // at this point tauxtrouve is potentially
^
(注意这是GCC 4.8.2,因为我没有安装4.9.x,但原则应该是相同的。)
这样可以检测到tauxtrouve
未初始化的事实。
但是,如果我们通过为tauxtrouve
添加初始值设定项来部分修复代码(但不是totaldiff
),那么gcc -std=c99 -Wall -O
会接受它而不会发出任何警告。这似乎是&#34; bug&#34;的一个实例。引自manual for GCC 3.3.6。
有一个问题是,这是否真的应该被视为一个错误:GCC不承诺捕获未初始化变量的每个可能实例。实际上,它不能以完美的准确度这样做,因为它是haccks's answer。因此,这样的警告在它们工作时可能会有所帮助,但是没有警告并不能证明您的代码没有未初始化的变量!它们实际上不能代替仔细检查自己的代码。
在halting problem中,有很多关于这个bug是否可以修复的讨论,或者是否试图检测这个特定的构造会导致其他正确代码的误报率不可接受。
答案 3 :(得分:2)
totaldiff
时正确生成以下错误消息:
error C4700: uninitialized local variable 'totaldiff' used
您应该考虑更新您的工作环境。
顺便说一句,这是我在编辑器中直接看到的内容: