首页
社区
课程
招聘
[翻译]关于C++编程的42条建议(完)
发表于: 2017-3-11 16:04 5426

[翻译]关于C++编程的42条建议(完)

2017-3-11 16:04
5426



这个bug是在Miranda NG的项目中发现的。代码中包含的错误被PVS-Studio分析器诊断为:V502 可能'?:' 操作的结果和预期的有差。'?:' 的优先级低于'|'



解释


我们有看到过很多例子,即使代码逻辑不对也能运行。这一次,我想提出一个不一样的、发人深思的话题来讨论。有时我们会看到完全不正确的代码很偶然的,即使困难重重,还是运行了。现在,对于有经验的程序员来说,这没什么好惊讶的(这是另一个故事了),但对那些C/C++的初学者来说,这就有点令人感到困惑了。所以,今天我们来看看这样的例子。


在上面给出的代码中,我们需要调用带有一定标志的CheckMenuItem(); 而且,猛地一看我们会觉得,如果bShowAvatartrue,那就做或运算MF_BYCOMMAND | MF_CHECKED,相反的,如果是false,就是MF_BYCOMMAND | MF_UNCHECKED。很简单。


在上面给出的代码中,程序员选用了最意料之中的三元运算符来表达这个意思(这个表达式是if-then-else的简便版本):



但问题是,|’的优先级高于?: ’的(参看C/C++中的运算优先级)。这样就导致了一下子有两个错误。


第一个错误是,条件改变了。它不再是——像某人可能理解的——"dat->bShowAvatar",而是"MF_BYCOMMAND | dat->bShowAvatar"


第二个错误——只剩下一个标志可以选择——不是MF_CHECKED 就是MF_UNCHECKED.标志MF_BYCOMMAND已经缺失了。

尽管有这些错误,代码还是正确运行了!原因——踩了狗屎运。那个程序员很幸运,因为标志MF_BYCOMMAND等于0x00000000L。而因为MF_BYCOMMAND等于0,所以最后没有影响到代码。可能一些有经验的程序员已经知道我想要表达的意思了,但我还是再做写解释吧,以便初学者理解。


首先,让我们看一眼加了括号的正确表达式。



然后用具体的值来代替宏:



如果|’的其中一个操作数是0,那么我们就可以简化上面的表达式,得到:



现在我们再来看原先那个不正确的表达式:



把具体的值代入:



在子表达式"0x00000000L | dat->bShowAvatar" 中,其中一个操作数是0。简化可得:



最后,我们得到了相同的表达式,这就是为什么有错误的代码还是能正确运行了。又一个编程奇迹发生了。


正确代码


有很多种方法来修正这段代码。其中一种就是加括号,另一种——加一个中间变量。还有比较老的if运算也是可以的:



我其实并不坚持让你用这个方法来修正代码。这样看上去是更易读了,但是有点长。所以,这更多的是偏好的问题。


建议


我的建议很简单——避免使用太复杂的表达式,尤其是三元运算符。还有,别忘了加括号。


就像在第四章已经说过的,?: 这个运算真的很危险。有的时候一疏忽你就忘了它的优先级非常的低,然后你就会写一个不正确的表达式。人们会在他们想要阻塞一个字符串的时候用到它,所以,你别这么做。



读了那么长一篇由静态代码分析器开发者所写的文章,却没有看到关于使用它的建议,是不是很奇怪。所以我们现在就讲这个。

下面的代码选自Haiku项目(BeOS的继承者)。代码中包含的错误被PVS-Studio诊断为:V501.'<' 表达式左右两边的操作数是一样的:lJack->m_jackType < lJack→m_jackType



解释


这是一个很常见的拼写错误。在右边应该是rJack,然后被错写成了lJack


这个拼写错误实际上是很简单的一种,但这种情况真的蛮复杂的。因为无论是编程风格,或者是其他的方法,在这里都无济于事。有的时候就是在拼写的时候犯了个错误,对此,你能做什么呢?


要特别强调说这不是某个人或者项目的问题。无疑,世人皆会出错,即使是在重大项目中的专业人员也会。是关于我这个言论的证明。你可以看到最简单的拼写错误,诸如A == A,Notepad++, WinMerge, Chromium, Qt, Clang, OpenCV, TortoiseSVN, LibreOffice, CoreCLR, Unreal Engine 4等等这些项目中出现。


所以这个问题是真实存在的,而且这不是学生的实验课作业。当有人跟我说,有经验的程序员是不会犯这样的错误的时候,我一般都会把这个链接甩给他们。


正确代码



建议


首先,让我们先来讲几个不那么有用的小贴士。


在编程的时候要特别小心,不要让错误溜进你的代码。(话是挺漂亮的,然而并没有什么用)。

用好的编程风格。(没有哪一种编程风格能帮你避免在变量名上出错。)


那么,有用的建议呢?


代码审查

单元测试(测试驱动开发「TDD」)

静态代码分析

我应该说,每一种方法都有各自的强处和弱处。这就是为什么要写出最高效可靠的代码是把它们都用起来。


代码审查能帮我们找到大量的,不同的错误,而且最重要的是,它可以帮我们提高代码的可读性。但不幸的是,分享式阅读(Shared reading)代码有点昂贵、无聊而且不会保证正确性。很难一直保持警惕,然后在阅读这种代码的时候发现拼写错误:



理论上,单元测试应该能帮到我们。但仅仅是理论上。在实际中,检查所有可能的运行路径是不现实的。而且,测试本身也会有一些错误 :)


静态代码分析仅仅是个程序,而非人工智能。分析器可能会跳过一些错误,有时还会误报,就是其实代码是正确的,但它弹出了错误信息。尽管有这些缺点,它还是一个很有用的工具的。在编写代码的早期,它可以检测到很多错误。


静态代码分析器也可以用做便宜版本的代码审查。让代码分析器而不是程序员来检查代码,也可以让它更全面的检查某一代码片段。


当然,我会推荐使用PVS-Studio静态代码分析器,这是我们开发的。额,这世上并非只有这一款。还有很多免费或付费的工具可以使用。比如说,你可以看看免费开放的Cppcheck分析器。维基百科上也给出了一系列静态代码分析器的单子:List of tools for static code analysis.


注意:


如果不正确使用静态代码分析器,可能会让你有点头疼。最典型的错误之一就是把检查模式选项设为最大值,然后就陷入大量的提示信息之中。我能给出的众多建议中的一个就是,为获得更多选项 ,去看看A, B会有用的。

静态分析器的使用应该有个界限,不是一直使用,或者当遇到问题的时候使用。参看C, D的解释。

真的,尝试着去使用静态代码分析器。你会喜欢它的。它是一个非常好的工具。

最后,我推荐阅读John Carmack的文章:静态代码分析.



假设你要在你的项目中实现X功能。软件开发理论家会说,你要用已经存在的库Y来实现你需要的功能。事实上,这是软件开发中一个典型的方法——重用你自己或者其他人之前已经创建好的库(第三方库)。然后大多数程序员也用这个方法。


但是,在一些文章或书籍中那些理论家忘了说,用某些第三方库近10年会有多悲剧。


我非常建议避免在项目中增加新的库。但是也别误解我。我不是让你丝毫都不用库,自己去实现所有的功能。这当然很没必要啊。但有时有些程序员心血来潮,想要往项目里加点cool’的特性,然后就引入了新的库。难的不是往项目里加新的库,而是从此以后整个项目要不得不带上它。


追溯几个大项目的演化,我看到了很多有第三方库引起的问题。我可能只会列举其中的一些,但这个单子已经能够引发我们的思考了:


引入新的库会立即增加项目的大小。在我们这个有快速互联网和大型SSD驱动的时代,这当然不是什么大问题。但当从版本控制系统下载时间从1变成10的时候,这就令人有点不快了。

即使你只用到了库功能的1%,你还是得把它整个都导入到项目中。结果就是,如果这些库是用是在编译模块使用的(比如,DLL),分布大小会很快增长。如果你把库当作源代码使用,那编译时间会大大增长。

跟项目的编译(compilation)连接的设备也会变得更复杂。有些库需要额外的组件。一个简单的例子:我们需要用Python来编译链接成目标文件(building)。结果就是,有时你需要很多额外的程序才能创建这个项目。而且有些地方会出错的几率也会上升。这很难解释,你需要自己去经历。在大项目中,有的地方就是一直运行不起来,你就得花很大的精力去解决它,让所有的一切都能正常编译运行。

如果你有担心漏洞,你要时常更新第三方库。那些违反者(violator)应该对这个很有兴趣,研究代码库寻找漏洞。首先,很多库都是开源的,其次,如果发现了其中一个库的弱点,你可以写一个exploit去利用那些用到这个库的应用程序。

那些库有可能突然就改变了许可证类型。第一,你要将这件事时刻放在心上,还要保持跟进。第二,如果真的发生了,你要做什么其实也不太清楚。比如说,有一次,广泛使用的softfloat库从个人协议移到了BSD

在升级编译器的版本的时候你会遇到困难。肯定会有一些库没有跟新的编译器兼容,那你只能等了,或者你自己连接那个库。

在移到不同的编译器上的时候,你也会遇到问题。比如说,你以前用的是Visual C++,现在打算用Intel C++。可定会有一些库出错。

在移到不同平台的时候,也会遇到问题。有的时候甚至不是一个完全不同的版本。比如说,你打算把一个Win32应用程序移到Win64上。你就会遇到同样的问题。最可能的是,有些库没准备好,你要想要怎么处理他们。最令人不悦的就是,有些库根本就不开发了,也没有更新它了。


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 1
支持
分享
最新回复 (13)
雪    币: 47147
活跃值: (20450)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2

赞!文章比较长,翻译比较辛苦,感谢与大家分享!

2017-3-11 16:21
0
雪    币: 367
活跃值: (302)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
2017-3-11 20:23
0
雪    币: 562
活跃值: (4347)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
4
辛苦了
2017-3-12 18:35
0
雪    币: 2325
活跃值: (4908)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
厉害··辛苦啊·!!~
2017-3-12 21:31
0
雪    币: 111
活跃值: (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
辛苦了,赞
2017-3-13 11:28
0
雪    币: 111
活跃值: (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
第二条的几率是1/256,楼主知道这个怎么算出来的吗
2017-3-13 12:20
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
谢谢楼主的分享,要好好学习一下。
2017-3-13 15:41
0
雪    币: 34
活跃值: (864)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
9
flyliying 第二条的几率是1/256,楼主知道这个怎么算出来的吗
我好像翻译错了,原文是“in 1 out of 256 cases the procedure of comparing hash with an expected value always returns true”,我开始遇到的时候比较不能理解,看了一些例句觉得可以取“1/256”的意思,现在细想,应该是错了。可以翻译为:在某一个不属于这256个数(-128,127)的例子中,比较过程的结果都会是“true”。。。好尴尬。 谢谢你那么认真的看,还指出来了。
2017-3-13 17:15
0
雪    币: 111
活跃值: (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
谢谢楼主的回复,我又看了次mysql的源码,理解如下: https://github.com/twitter/mysql/blob/mysql-5.1.53/sql/password.c 中直接返回的是memcpy的结果, 其中 typedef char my_bool; https://github.com/twitter/mysql/blob/mysql-5.1.53/sql/sql_acl.cc 这里直接判断my_bool结果是不是0来作为成功条件的. 但是在一些平台下,返回值会超出 {-128…127}。char = (int) xx;意味着返回的数是被截断的,也就是说只要二进制后8位为0的整数都会被认为是成功的,这样的数占所有整数的1/256.不知道这样理解是否符合
2017-3-13 20:56
0
雪    币: 34
活跃值: (864)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
11
flyliying 谢谢楼主的回复,我又看了次mysql的源码,理解如下: https://github.com/twitter/mysql/blob/mysql-5.1.53/sql/password.c 中直接返回 ...
在一些平台,是指int占两个字节的平台吗?然后int的个数有2的16次方,然后后8位为0的有2的八次方个,所占比例是1/256这样理解吗?
2017-3-13 22:05
0
雪    币: 6108
活跃值: (3087)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
谢谢楼主翻译,再次感谢
2017-3-14 05:28
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
支持!很多建议在实践中都感受过,非常有用的建议。
2017-3-21 21:33
0
雪    币: 137
活跃值: (1548)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14

感谢翻译
看了下文章,  想吐槽一下:&quot;出于这个这个目的,他调用了empty()函数&quot;--我在想他写代码是靠猜的吗?感觉作者自己的毛病硬要说成C++的建议,  看来国外也有水货大佬嘛

2017-6-20 11:18
0
游客
登录 | 注册 方可回帖
返回
//