翻译《有关编程、重构及其他的终极问题?》——1.别把编译器的事给做了
标签(空格分隔): 翻译 技术 C/C++
作者:Andrey Karpov
翻译者:顾笑群 - Rafael Gu
校验者:高国栋
最后更新:2016年11月12日
本书背景说明、总目录等介绍,可以跳转到以下链接进行查看:
http://blog.csdn.net/headman/article/details/53045891欢迎大家转载,但请附上原作者以及翻译者的名字、原文出处,以尊重光荣的劳动者。
1.别把编译器的事情给做
考虑下面这段从MySQL项目里抽取出来的源代码。这段代码包含的错误被PVS-Studio分析诊断为:V525 The code containing the collection of similar blocks. Check items ‘0’,’1’,’2’,’3’,’4’,’1’,6’ in lines 680,682,689,691,693,695.(译者注:这段应该是PVS-Studio的分析输出,不翻译了,大家注意那个Check items后面4和6之间是1,而不是5,这个就是一个隐藏的错误)。
static int rr_cmp(uchar *a, uchar *b)
{
if (a[0] != b[0])
return (int)a[0] - (int)b[0];
if (a[1] != b[1])
return (int)a[1] - (int)b[1];
if (a[2] != b[2])
return (int)a[2] - (int)b[2];
if (a[3] != b[3])
return (int)a[3] - (int)b[3];
if (a[4] != b[4])
return (int)a[4] - (int)b[4];
if (a[5] != b[5])
return (int)a[1] - (int)b[5]; <<<<====
if (a[6] != b[6]
return (int)a[6] - (int)b[6];
return (int)a[7] - (int)b[7];
}
解释
这是一个经典的错误,主要是因为拷贝粘帖代码段导致。很明显,这位程序员在拷贝粘帖代码段“return (int)a[1] - (int)b[1];”时,在改变数组下标时,忘了把a数组的下标“1”改成“5”。这就导致这个比较函数某些情况下会返回一个错误的值;这个错误一旦犯下,在后续是比较难被注意的。在我们用PVS-Studio扫描MySQL代码前,所有的测试都没有发现,所以这个问题是很难被检测到的。
正确的代码
if (a[5] != b[5])
return (int)a[5] - (int)b[5];
建议
虽然上面的代码很整齐且易读,但它并不能阻止开发者忽略这个错误。当读到这样的代码时,因为你看到的代码都是类似的块,而且很难在每个块上都集中注意力,所以你几乎很难发现这个错误。
这样类似的代码块,看上去很像是这位程序员尽了最大的限度去主动优化后的结果,他把循环“给展开了”(译者注:有时展开循环能提高执行效率)。但我并不认为在这里是一个好主意。
首先,我怀疑这位程序员是否实现了其主动优化执行效率的目的。现代的编译器非常聪明,如果发现可以提高代码的执行效率,它就非常擅长自动把循环展开,而不需要你自己手工这么做。
其次,因为尝试优化代码而导致这个bug,但如果你些一个简单的循环,那么这个错误发生的机会就小的多。
我的建议是把以上函数写成如下形式:
static int rr_cmp(uchar *a, uchar *b)
{
for (size_t i = 0; i < 7; ++i)
{
if (a[i] != b[i])
return a[i] - b[i];
}
return a[7] - b[7];
}
好处:
- 这个函数很容易阅读和理解。
- 你大大降低了发犯错的机会。
我非常确信,这个函数不会比前面那个有错误的执行起来慢,因为现代的编译器会进行展开优化。
所以,我的建议是——写简单且易读的代码。这里有个规则:简单的代码通常就是正确的代码。不要尝试去做编译器的活——比如去展开循环。编译器无须你的帮助,就可以把绝大多数事情做好。这样的人工优化工作一般只有在少数特定极端的代码段才有效,特别是只有在确认这段代码执行真有的有问题(慢)的情况。