下面的代码来自 Appleseed 项目。代码中包含的错误被 PVS-Studio 诊断为: V719 switch 语句没有覆盖枚举 “ InputFormat” 的所有值,少了 InputFormatEntity.
解释
有的时候我们需要在已经存在的枚举里加入新的元素,当我们做这个操作的时候,我们需要特别的谨慎 —— 因为我们要检查全部代码看看哪里有用到这个枚举,比如说在 switch 和 if 中。上面给出的代码就是这种情况。
在 InputFormat 中加了 InputFormatEntity—— 这里是我想象的,因为确实在 InputFormat 的后面有加入这个常量。很多时候,程序员在枚举后面加了新的常量,然后忘了检查代码以确保他们有正确处理这个常量,也没有修改 switch 操作。
最后的结果就是,在这个例子中,并没有处理到 "m_format==InputFormatEntity" 的情况。
正确代码
建议
让我们想想,怎样在代码重构的时候避免这种错误?最简单,但不那么有效的解决方法就是加一个 ‘ default’ ,它可以输出一个信息,像这样:
现在,如果变量 m_format 是 InputFormatEntity ,我们就可以看到一个异常。这样的处理方法有两个不好的地方:
因为有可能在测试阶段这个错误并没有显现出来(如果在测试的时候, m_format 不等于 InputFormatEntity ),那这个错误就会流入发布版本,以后才会显现—— 在客户运行时出现。如果要顾客来反映这种问题,真的很糟糕。
如果我们把 default 考虑为一个错误,那我们就不得不写一个 case 来解决枚举所有可能的值。这样就很不方便,尤其是当一个枚举中有很多常量的时候。有时,用 default 来处理不同 case 真的很方便。
我建议用以下的方法来解决这个问题,我不敢说这个方法很完美,但至少它有解决到问题。
当你定义一个枚举的时候,要确保你也加了一条特殊的注释。你也可以用关键词和枚举名。
例子:
在上面的代码中,当你要改变枚举 InputFormat ,你就可以直接在项目的源代码中查找“ ENUM:InputFormat” 。
如果你是在一个开发者团队中,你可以告诉你的小伙伴们这个约定,然后把它加入到你们的编程标准和风格指引中。如果有人没能遵守这条原则,真遗憾。
我想在看了枚举类型的错误后,你肯定很累了。所以这次,让我们休息一会,不看代码了。
一个典型的场景 —— 你们的项目没有正确运行。但你也不知道发生了什么。在这种情况下,我建议你不要急于去责备某个人,而应该把焦点放到你们的代码上。在 99.9% 的案例中,罪恶之源就是你们的开发团队中某个人引入了 bug 。通常这个 bug 都非常的愚蠢且平淡无奇。所以快点花点时间去找找。
实际上,这个一直出现的 bug 也不能说明什么。你可能只是遭遇了 海森堡 bug ( Heisenbug ) .
责备编译器也不是什么好主意。它当然也会出错啊,虽然真的非常少。比如说,你会发现就是仅仅错用了 sizeof(),这件事就会变得很棘手。我的博客里有篇博文就是关于它的: The compiler is to blame for everything 。
但为了让记录更公正一点,我应该说,还是会有例外存在的。很少的情况下, bug不对代码做什么。但我们还是应该注意到这种可能性的存在。这可以帮助我们保持理智。
我会用一个我曾经遭遇过的例子来证明这种可能性。非常幸运,我当时有截图。
当时,我在做一个简单的测试项目,想要去演示 Viva64分析器( PVS-Studio的前身)的功能,但是它没能正确运行。
经过漫长而令人讨厌的调查之后,我发现是一个内存槽引起这所有的问题。更确切地说,是 1bit。你可以看看下面的照片,我当时正在调试,想往这个存储单元写入‘ 3’。
在改变内存之后,编译器开始读值并将其显示在窗口上,但它显示的是 2:看,地址是 0x02。尽管我已经把值设置为 3了。低位总为 0.
一个内存测试项目 证实了这个问题。非常奇怪,这台电脑一直都工作得好好的,没出什么问题。最后为了让项目正确运行,我换了内存条。
我真的很幸运。当时我处理的是一个简单的测试项目。虽然我还是花了很多时间去了解发生了什么。我花了两个多小时去检查汇编器目录,想要找到引起这种奇怪行为的原因是什么。是的,我当时有因此责备编译器。
我无法想象,如果是一个实际的项目,我要花多大的时间精力去解决这个问题。谢天谢地,我当时不用去调试其他的东西。
建议
要时刻检查你代码中的错误。别想着推卸责任。
但是,如果一个 bug 只在你的电脑上出现,而且都快一周了。那么,可能不是因为你的代码。
要持续寻找 bug 。但是在回家前,可以运行一个一整夜的 RAM 测试。或许,这个简单的测试步骤能节省你的时间精力。
下面的代码来自 Haiku 项目( BeOS 的继承者)。代码中包含的错误被 PVS-Studio 诊断为: V696. 'continue'操作会终止 'do { ... } while (FALSE)' 循环,因为判断条件一直是 false。
解释
在 do-while 循环里的 continue 的运作机制可能跟某些程序员想象的不一样。当遇到 continue 的时候,会先要检查循环的终止条件。我想要更详细的解释这个问题。假设有个程序员写了这么一段代码:
或者是这样的:
很多程序员凭直觉理解,当遇到 continue ,就要(重新)评估控制条件( i<n ) , 然后只有为真的时候才会进入下一个循环迭代。但,如果程序员的代码是这么写的:
直觉就不起作用了哦。因为他们没有在 continue 上面看到判断条件,而且,似乎对于他们来说, continue 会马上触发下一个循环迭代。事情并非如此, continue 还是像它平常所做的那样—— 先重新评估控制条件。
除非踩了狗屎运,不然缺乏对 continue 的理解真的很难不出错。无论如何,如果循环条件 一直是 false ,这个错误肯定会发生。因为在上面给出的代码中,程序员打算在之后的循环中执行特定的操作。代码中的注释“ //try again ” 证明了他们有这个意图。当然不会有‘ again’ 啦,因为判断条件一直是 false ,而且一遇到 continue ,循环就会终止哦。
换句话说,在这个 do {...} while (false) 结构中, continue 就相当于 break 。
正确代码
要写正确代码有很多选项。比如,创造一个无限循环,然后用 continue 继续,用 break 退出。
建议
尝试着不要在 do { ... } while (...) 用 continue 。即使你真的知道这是如何运作的。因为一不小心你就会犯这种错误,而且 / 或者你的同时就不能正确理解代码啦,最后把它错改。我一直都这么说:一个优秀的程序员不是知道并使用其他语言技巧的那一个,而是能够写出干净易懂的,即使新手也能理解的代码的那一个。
新的 C++标准引入很多有用的改变。有些我现在还没有用到,但是有些应该马上用起来,因为用它们有诸多益处。
其中一个现代化标准就是关键字 nullptr , 其旨在代替 NULL宏。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
上传的附件: