【问题标题】:Compiler not detecting obviously uninitialized variable编译器未检测到明显未初始化的变量
【发布时间】:2015-01-19 17:39:26
【问题描述】:

我尝试过的所有 C 编译器都不会在下面的代码 sn-p 中检测到未初始化的变量。然而,这里的情况很明显。

不用担心这个 sn-p 的功能。这不是真正的代码,为了调查这个问题,我把它删掉了。

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 with -Wall -WExtra
  • 启用所有警告的 Microsoft Visual C++ 2013

【问题讨论】:

  • 我不知道,但也许它的编译器优化?我也很想知道。希望我们能尽快得到答案。
  • 如果将-pedantic 标志添加到gcc 会发生什么?
  • 我不熟悉如何测试初始化​​,但请查看我通过谷歌搜索找到的gcc.gnu.org/wiki/Better_Uninitialized_Warnings。特别是CCP assumes a value for uninitialized variables。如果我对自己有把握,我会做出这样的评论和回答。
  • Visual Studio 2013 告诉我“错误 C4700:使用了未初始化的局部变量 'totaldiff'”。

标签: c


【解决方案1】:

这个变量没有被初始化的明显性被夸大了。路径分析需要时间,而您的编译器供应商要么不想实现该功能,要么认为这会花费您太多时间——或者您只是没有明确选择加入。

例如,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
}

【讨论】:

  • 你有关于退化的数据吗?供您参考,LLVM IR 基于 SSA 表示法,顾名思义,它只允许对任何单个变量进行一次赋值(通常,源代码中的变量名被重用,后缀为索引)。结果,路径是必然物化的,因此检查一个变量是否可能未初始化使用是微不足道的:对于每个变量,在计算其赋值时,检查它是否已明确初始化、潜在未初始化或明确未初始化。使用时,适当警告。
  • @Matthieu 哦,实际上远没有人们想象的那么简单。 [见这里](gist.github.com/voo42/293d3cafa820f8c86e54 一个简单的例子,你的提议会给出虚假警告。当然可行,但仅仅使用 SSA 表单是不够的。我实际上不确定是否有一个不涉及检查所有的通用解决方案通过函数的可能路径。
  • @Voo 没有不涉及到 HP 的。考虑if (foo(0)) { i = 0 } if (bar(0)) { j = i }。但这需要bar(0) =&gt; foo(0),所以我们需要知道bar(0) 和foo(0) 的值 - 由于HP,如果不实际运行程序(并希望它终止),这是不可能的。
  • @Voo:哦,我根本不是,这意味着 100% 准确的预测是可能的。这就是我有 3 状态的原因:“可能未初始化”恰好适合您描述的情况。这就是为什么 Clang 有 2 警告的原因:-Wuninitialized(默认激活)仅对绝对未初始化的变量发出警告,而 -Wmaybe-uninitialized(默认未激活)仅对已初始化的变量发出警告可能未初始化。所以是的,后者可能会对复杂的代码发出虚假的警告;我认为你最好修复代码,但你可以忽略它。
  • IR其实很简单,如果你忽略噪音:) 例如,这里有2个tauxtrouve:tauxtrouve.0和tauxtrouve.1。 phi magic 根据前任选择值:phi i32 [ %value, %5 ], [ %tauxtrouve.0, %3 ] 产生 i32 类型的值,如果前任是名为 5 的标签,则它产生 %value,如果它是名为 3 的标签它产生tauxtrouve.0; undef是一个特殊的值,表示缺少初始化,而br是一个有条件的goto...
【解决方案2】:

是的,它应该对那个未初始化的变量发出警告,但它是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 周年!

【讨论】:

  • 天啊 .. 一个有十年历史的 bug,其 cmets 列表读起来就像是 OSS 项目中 snark-iness 最恐惧的体现
  • @PeterM;不幸的是,这个存在 10 年之久的错误尚未修复!
  • #WontFix #NotSexyEnoughToWorryAbout
  • @Lundin 您在回答中写道,您“不能指望”编译器检测错误并且“编译器只有两个任务”。事实上,这在 20 年前是正确的,而现在则不那么正确了。编译器已经发生了巨大的变化,如今人们依靠编译器来检测明显的错误。尽管这具有未定义的行为,但编译器生成警告会很好(并且可以预期)。不是为了定义UB,而是让程序员知道他做错了。这可能不是技术错误,但绝对是用户体验错误。
  • 我看到自己将来会读到这个:“..由于历史原因,这个错误没有解决。”
【解决方案3】:

此答案仅针对 GCC。

经过进一步调查和 cmets,比我之前的回答更多。这段代码 sn-p 有两个未初始化的变量,每个变量都因不同的原因未被检测到。

首先,-Wuninitialized 选项的 GCC documentation 表示:

因为这些警告取决于优化,所以有警告的确切变量或元素取决于精确的优化选项和使用的 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 中引用的“错误”的一个实例。

关于这是否真的应该被视为错误存在一些问题:GCC 不承诺捕获未初始化变量的所有可能实例。确实,它不能完全准确地做到这一点,因为那是halting problem。所以像这样的警告在它们起作用时会很有帮助,但是没有警告并不能证明你的代码没有未初始化的变量!它们确实不能替代仔细检查您自己的代码。

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

【讨论】:

  • 我相信OP更关心检测totaldiff没有被初始化。
  • @PeterM:好点子。这里实际上有两个不同的问题,我已经重写了我的答案来解决这两个问题。
【解决方案4】:

Michael,我不知道您在哪个版本的 Visual Studio 2013 上尝试过这个,但它肯定已经过时了。 Visual Studio 2013 Update 4 在首次使用 totaldiff 时正确生成以下错误消息:

error C4700: uninitialized local variable 'totaldiff' used

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-11
    • 2013-05-14
    • 1970-01-01
    • 1970-01-01
    • 2013-04-15
    • 2012-07-18
    • 1970-01-01
    • 2017-10-05
    相关资源
    最近更新 更多