2014-11-21 182 views
58

我试过的所有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"警告。

我想这些编译:

  • GCC 4.9.2与-Wall -Wextra
  • 微软的Visual C++ 2013中的所有警告启用
+2

我不知道,但也许它的编译器的优化?我也很想知道。希望我们很快会得到答案。 – 2014-11-21 14:35:36

+0

如果将'-pedantic'标志添加到gcc会发生什么? – avgvstvs 2014-11-21 14:35:49

+1

我并不熟悉初始化是如何测试的,但请查看https://gcc.gnu.org/wiki/Better_Uninitialized_Warnings,这是我通过Google搜索发现的。特别是'CCP为未初始化的变量赋值“。如果我确信自己,我会发表评论和回答。 – 2014-11-21 14:42:54

回答

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 /铛,以找到使用-的-未初始化值不化合物,因为由IR使用的SSA符号作为嵌套的增加所需要的路径分析。

这不像我希望的那样简单,像“-S -emit-llvm”,但我找到了他描述的SSA-notation输出。老实说,我对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 
} 
+7

你有关于退化的数字吗?为了您的信息,LLVM IR基于SSA符号,顾名思义,它只允许对任何单个变量进行单一赋值(通常,源代码中的变量名称会被索引后缀重复使用)。因此,路径必须物化,并检查一个变量是否可能被使用未初始化,因此是微不足道的:对于每个变量,在计算它的赋值时,检查它是否明确初始化,可能未初始化或未初始化。使用后,应适当警告。 – 2014-11-21 17:42:48

+1

@Matthieu哦实际上远不像人们想象的那么微不足道。 [见这里](https://gist.github.com/voo42/293d3cafa820f8c86e54)举个简单的例子,你的建议会给出虚假的警告,当然可以,但只使用SSA表单还不够,我其实不确定是否有一个通用的解决方案,不涉及通过函数检查所有可能的路径 – Voo 2014-11-22 22:54:35

+1

@Voo没有一个不涉及到HP的运行,考虑if(foo(0)){i = 0} if(bar (0)){j = i}'但是这需要'bar(0)=> foo(0)',所以我们需要知道'bar(0)'和'foo(0)'的值为'这是不可能的,因为没有真正运行一个程序(并希望它终止),由于惠普 – 2014-11-23 00:00:22

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

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

+4

Geez ..一个十岁的bug,有一个评论列表,类似于OSS项目中对“snark-iness”最糟糕恐惧的体现 – 2014-11-21 14:49:47

+3

@PeterM;不幸的是,这个10岁的bug尚未修复! – haccks 2014-11-21 14:51:45

+3

#WontFix #NotSexyEnoughToWorry关于 – 2014-11-21 14:53:57

11

此答案只适用于GCC。

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

首先,在GCC documentation-Wuninitialized选项说:

因为这些警告取决于优化,精确的变量或元素,其有警告取决于所使用的精确优化选项和GCC的版本。

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

这些警告仅在优化编译时才有可能,因为它们需要仅在优化时计算的数据流信息。如果你没有指定-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接受它没有任何警告。这似乎是haccks's answer中引用的“bug”的一个实例。

这是否应该真的被认为是一个错误有一些问题:GCC不承诺捕捉未初始化变量的每个可能的实例。事实上,它不能以完美的准确性来做到这一点,因为那是halting problem。所以这样的警告在工作时会有帮助,但是没有警告并不能证明你的代码没有未初始化的变量!它们确实不能替代仔细检查自己的代码。

bug report linked by haccks中,有很多关于错误是否可修复的讨论,或者试图检测这个特定的构造是否会导致其他正确代码的不可接受的错误肯定率。

+1

我相信OP更关心检测'totaldiff'没有被初始化。 – 2014-11-21 19:12:43

+0

@PeterM:好点。实际上有两个单独的问题,我已经重写了我的答案,以解决这两个问题。 – 2014-11-22 18:42:30

2

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

error C4700: uninitialized local variable 'totaldiff' used 

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

到,这里的方法是什么,我直接看到编辑器:

Visual Studio 2013 caught the error

相关问题