C++是一种复杂的编程语言,其中充满了各种微妙的陷阱。在 C++ 中几乎有数不清的方式能把事情搞砸。幸运的是,如今的编译器已经足够智能化了,能够检测出相当多的这类编程陷阱并通过编译错误或编译警告来通知程序员。最 终,如果处理得当的话,任何编译器能检查到的错误都不会是什么大问题,因为它们在编译时会被捕捉到,并在程序真正运行前得到解决。最坏的情况下,一个编译 器能够捕获到的错误只会造成程序员一些时间上的损失,因为他们会寻找解决编译错误的方法并修正。
那些编译器无法捕获到的错误才是最危险的。这类错误不太容易察觉到,但可能会导致严重的后果,比如不正确的输出、数据被破坏以及程序崩溃。随着 项目的膨胀,代码逻辑的复杂度以及众多的执行路径会掩盖住这些 bug,导致这些 bug 只是间歇性的出现,因此使得这类 bug 难以跟踪和调试。尽管本文的这份列表对于有经验的程序员来说大部分都只是回顾,但这类 bug 产生的后果往往根据项目的规模和商业性质有不同程度的增强效果。
这些示例全部都在 Visual Studio 2005 Express 上测试过,使用的是默认告警级别。根据你选择的编译器,你得到的结果可能会有所不同。我强烈建议所有的程序员朋友都采用最高等级的告警级别!有一些编译提示在默认告警级别下可能不会被标注为一个潜在的问题,而在最高等级的告警级别下就会被捕捉到!(注:本文是这个系列文章的第 1 部分)
1)变量未初始化
变量未初始化是 C++ 编程中最为常见和易犯的错误之一。在 C++ 中,为变量所分配的内存空间并不是完全“干净的”,也不会在分配空间时自动做清零处理。其结果就是,一个未初始化的变量将包含某个值,但没办法准确地知道 这个值是多少。此外,每次执行这个程序的时候,该变量的值可能都会发生改变。这就有可能产生间歇性发作的问题,是特别难以追踪的。看看如下的代码片段:
if (bValue) // do A else // do B
如果 bValue 是未经初始化的变量,那么 if 语句的判断结果就无法确定,两个分支都可能会执行。在一般情况下,编译器会对未初始化的变量给予提示。下面的代码片段在大多数编译器上都会引发一个警告信息。
int foo () { int nX; return nX; }
但是,还有一些简单的例子则不会产生警告:
void increment (int &nValue) { ++nValue; } int foo () { int nX; increment (nX); return nX; }
以上的代码片段可能不会产生一个警告,因为编译器一般不会去跟踪查看函数 increment ()到底有没有对 nValue 赋值。
未初始化变量更常出现于类中,成员的初始化一般是通过构造函数的实现来完成的。
class Foo { private: int m_nValue; public: Foo (); int GetValue () { return m_bValue; } }; Foo::Foo () { // Oops, 我们忘记初始化m_nValue 了 } int main () { Foo cFoo; if (cFoo.GetValue () > 0) // do something else // do something else }
注意,m_nValue 从未初始化过。结果就是,GetValue ()返回的是一个垃圾值,if 语句的两个分支都有可能会执行。
新手程序员通常在定义多个变量时会犯下面这种错误:
int nValue1, nValue2 = 5;
这里的本意是 nValue1 和 nValue2 都被初始化为5,但实际上只有 nValue2 被初始化了,nValue1从未被初始化过。
由于未初始化的变量可能是任何值,因此会导致程序每次执行时呈现出不同的行为,由未初始化变量而引发的问题是很难找到问题根源的。某次执行时, 程序可能工作正常,下一次再执行时,它可能会崩溃,而再下一次则可能产生错误的输出。当你在调试器下运行程序时,定义的变量通常都被清零处理过了。这意味 着你的程序在调试器下可能每次都是工作正常的,但在发布版中可能会间歇性的崩掉!如果你碰上了这种怪事,罪魁祸首常常都是未初始化的变量。
2)整数除法