我尝试过的所有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.
}
Run Code Online (Sandbox Code Playgroud)
另一方面,如果我发表评论tauxtrouve = value ;,我会收到"local variable 'tauxtrouve' used without having been initialized"警告.
我试过这些编译器:
Bri*_*ain 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.
Run Code Online (Sandbox Code Playgroud)
这些天真例子的执行时间差异可以忽略不计.但想象一下翻译单元有数千行,数十个函数,每个函数都有循环和重嵌套.路径的数量很快变得复杂并且成为分析是否在该比较之前是否将发生分配的第一次循环的大负担.
编辑:@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
}
Run Code Online (Sandbox Code Playgroud)
hac*_*cks 50
是的,它应该对该未初始化的变量发出警告,但这是一个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;
}
}
Run Code Online (Sandbox Code Playgroud)
并确诊-O2 -W -Wall.
不幸的是,今年是这个bug的10周年!
Nat*_*dge 11
这个答案只针对GCC.
经过进一步的调查和评论后,比我之前的答案还要多.此代码段有两个未初始化的变量,并且由于其他原因,每个变量都未被检测到.
首先,该选项的GCC文档-Wuninitialized说:
由于这些警告取决于优化,因此存在警告的确切变量或元素取决于所使用的GCC的精确优化选项和版本.
以前版本的GCC手册更明确地说明了这一点.以下是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
^
Run Code Online (Sandbox Code Playgroud)
(注意这是使用GCC 4.8.2,因为我没有安装4.9.x,但原则应该是相同的.)
这样可以检测到tauxtrouve未初始化的事实.
但是,如果我们通过为tauxtrouve(但不是for totaldiff)添加初始化程序来部分修复代码,则gcc -std=c99 -Wall -O接受它而不发出任何警告.这似乎是haccks答案中引用的"bug"的一个例子.
关于这是否真的应该被视为一个错误存在一些疑问:GCC不承诺捕获未初始化变量的每个可能实例.实际上,它不能以完美的准确度这样做,因为这是暂停的问题.因此,这样的警告在它们工作时可能会有所帮助,但是没有警告并不能证明您的代码没有未初始化的变量!它们实际上不能代替仔细检查自己的代码.
在由haccks链接的错误报告中,关于该错误是否可以修复,或者是否尝试检测该特定结构将导致其他正确代码的不可接受的误报率,有很多讨论.
| 归档时间: |
|
| 查看次数: |
9039 次 |
| 最近记录: |