Visual Studio /analyze不好之处---漏报(三)
分析是一种强大的VisualC++特性,可以帮助发现bug。然而,它有时会在不存在问题的情况下发现问题。不幸的是,这些误报,就像这里报道的那样,使/分析变得不那么有用,因为严重的错误最终会被“狼”的叫声淹没。在这篇文章中,我描述了另一个令人沮丧的误报,它展示了一个编译器完全无法理解别名的例子。
VisualStudio2012中的/analyze不再对这些构造发出警告。但不幸的是,它们已经从假阳性变成了假阴性。在GetInt()示例中,如果注释掉了部分初始化,则不会出现警告,即使返回的是部分未初始化的对象。更好吗?我不确定。
这些测试都是用Visual Studio 2010 SP1,C/C++优化编译器版本16.00 .40219.01为80×86。
未初始化的数据
我们有一些代码可以从文件或网络流中读取并初始化各种变量。当我们需要初始化一个整数时,我们调用这个函数:
int GetInt(const char* p) { int result; char* pDest = (char*)&result; pDest[0] = p[0]; pDest[1] = p[1]; pDest[2] = p[2]; pDest[3] = p[3]; return result; // warning C6001: Using uninitialized memory ‘result’ }
编译器无法或不愿意告诉我们已经写入了结果的所有四个字节,因此在返回结果时会发出警告。
有人可能会说编译器在技术上是正确的。关于C++中的混叠的规则有点深奥,所以我相信这是合法的,但我不能肯定。使用联合是一种通知编译器正在发生别名的方法,因此我尝试:
union intAlias { int i; char c[4]; }; int GetIntFixAttempt(const char* p) { intAlias result; char* pDest = result.c; pDest[0] = p[0]; pDest[1] = p[1]; pDest[2] = p[2]; pDest[3] = p[3]; return result.i; // warning C6001: Using uninitialized memory ‘result’ }
没有骰子。但是我走的是正确的道路。接下来,我尝试像这样重写代码,使编译器更容易跟踪:
union intAlias { int i; char c[4]; }; int GetIntFixed(const char* p) { intAlias result; result.c[0] = p[0]; result.c[1] = p[1]; result.c[2] = p[2]; result.c[3] = p[3]; return result.i; // No warning! }
终于成功了!很高兴知道,我可以找到代码,让/analyze意识到我没有使用未初始化的内存,但我不想经历这样的体操。所有关于代码将如何执行的信息都是可用的,所以没有理论上的原因说明编译器不能正确地推理,但是它失败了。
如果编译器想给我一个关于错误别名的警告,那么我可以尊重它,但是它当前的警告是错误的,这使得/analyze更难使用。
为虫子生,为虫子死,为虫子奋斗一辈子