编译器没有检测到明显的未初始化的variables

我试过的所有C编译器都不会在下面的代码片段中检测到未初始化的variables。 然而这种情况在这里很明显。

不要担心这个片段的function。 这不是真正的代码,为了调查这个问题,我把它剥离了下来。

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"警告。

我试过这些编译器:

  • GCC 4.9.2与-Wall -WExtra
  • 启用了所有警告的Microsoft Visual C ++ 2013

这个variables未被初始化的显而易见性被夸大了。 path分析需要花费时间,而编译器供应商要么不想实现这个function,要么认为花费你太多的时间 – 或者你没有明确地selectjoin。

例如,用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. 

这些天真的例子在执行时间上的差异是微不足道的。 但想象一个具有数千行,数十个函数的翻译单元,每个函数都有循环和沉重的嵌套。 path的数量很快就会增加,并成为一个很大的负担,来分析循环的第一次迭代是否会在比较之前进行赋值。


编辑:@Matthieu指出,与LLVM /铛,find使用的未初始化值所需的path分析不复合,因为嵌套增加,因为红外使用的SSA符号。

它不像我希望的那样“ -S -emit-llvm ”那么简单,但是我find了他描述的SSA-notation输出。 我说实话,我对LLVM IR确定不够熟悉,但是我会拿Matthieu的话来说。

底线:使用clang --analyze ,或说服别人修复--analyze合作--analyze的错误。

 ; 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 } 

是的,它应该提出有关未初始化variables的警告,但这是一个GCC错误 。 这里给出的例子是:

 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诊断为-O2 -W -Wall

不幸的是,今年是这个bug的10周年!

这个答案只适用于GCC。

经过进一步的调查和评论,比以前的答案还要多。 这个代码片段有两个未初始化的variables,并且由于不同的原因它们都未被检测到。

首先, -Wuninitialized选项的GCC文档说:

由于这些警告依赖于优化,所以存在警告的确切variables或元素取决于所使用的GCC的精确优化选项和版本。

以前版本的GCC手册更明确地表述了这一点。 以下是GCC 3.3.6手册的摘录:

这些警告仅在优化编译时才有可能,因为它们需要仅在优化时计算的数据stream信息。 如果你不指定-O,你根本不会得到这些警告。

看来目前的版本可能会给出一些警告,没有未初始化的variables-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会在没有任何警告的情况下接受它。 这似乎是haccks答案中引用的“bug”的一个例子。

有一个问题是否应该真的被认为是一个错误:GCC不承诺捕获未初始化variables的每个可能的实例。 事实上,它不可能完全准确,因为这是停滞的问题 。 所以这样的警告在工作时会有帮助,但是没有警告并不能certificate你的代码没有未初始化的variables! 他们真的不能替代仔细检查自己的代码。

在由hacck链接的bug报告中 ,关于bug是否是可修复的,或者试图检测这个特定的构造是否会导致其他正确的代码的不可接受的误报率有很多讨论。

迈克尔,我不知道你试过这个版本的Visual Studio 2013,但它肯定是过时的。 在第一次使用totaldiff Visual Studio 2013 Update 4正确地产生以下错误信息:

 error C4700: uninitialized local variable 'totaldiff' used 

你应该考虑更新你的工作环境。

顺便说一下,这是我在编辑器中直接看到的内容:

Visual Studio 2013发现错误