《C专家编程》
A.3 C语言中不同的增值语句的区别何在
考虑下面四条语句:
x = x + 1;
++x;
x++;
x += 1;
显然,这四条语句的功能是相等,它们都是把x的值增加1.如果像现在这样不考虑前后的上下文环境,它们之间并没有什么区别。应试者需要(隐式或显示的)提供适当的上下文环境,以便回答这个问题并找出这四条语句之间的区别。注意,最后一条语句是一种在算法语言中表达“x等于x加上1”的便捷方法。因此,这条语句仅供参考,我们需要寻找的是其他三条语句的独特性质。
绝大多数C程序员可以立即指出++x是一种前缀自增,当它先增加x的值然后再在周围的表达式中使用x的值。而x++是一种后缀自增,它先在周围的表达式中使用x的值然后再增加x的值。有些人认为C语言存在++和--操作符的唯一原因是*p++在PDP-11机器上可以用一条单一的机器指令来表示。事实上并非如此,这个特性继承了PDP-7上的B语言,但自增和自减操作符在所有的硬件系统中的应用之广令人难以置信。
有些程序员则在此处未作深入考虑,忽视了当x不是一个简单的变量而是一个涉及数组的表达式时,像 x += 1这样的形式是很有用的。如果你有一个复杂的数组引用,并需要证明同一种下标形式在两种引用中都可以使用,那么:
node[ i >> 3 ] += -( 0x01 << ( i & 0x7 ) );
就是你应该采用的方法。这个例子是我直接从操作系统的代码中取出来的,只有数据名作为了改动。优秀的应试者还能够指出左值只被计算了一次。这一点非常重要,因为下面的语句:
mang[ i++ ] += y;
被当作
mang[ i ] = mang[ i ] + y;
i++;
而不是
mang[ i++ ] = mang[ i++ ] + y;
以前,当我们对一些申请Sun的Pascal编译器队伍的位置上候选人进行面试时,最好的那位候选人解释说这些区别于编译器的中间代码有关,例如++x表示取x的地址,增加它的内容,然后把值放在寄存器中;x++则表示取x的地址,把它的值装入寄存器中,然后增加内存中的x的值。顺便问一句,使用编译器术语,另外两条语句应该怎么描述?
尽管Kernighan 和 Ritchie 认为自增操作比直接加1更有效率,但目前所使用的当代编译器通常在这方面都做得很好,使这几种方法的速度都一样。如果没有任何能够显示它们之间区别的相关上下文环境,现代的编译器在编译这些语句时应该产生相同的指令。它们应该是增加一个变量时最快的一种指令。你可以在喜欢的编译器上编译这些代码,编译器应该有一个选项,可以产生一个汇编指令列表。你也可以把编译器设置为调试模式,这样也常常可以使检查对应的C语句和汇编指令更为容易。不要使用优化选项,因为这些语句有可能因为优化而被精简掉。
。。。。。。。。。。。。。。。。。。。(省略2段)
所以,有时候区别就在于哪一个在源代码中看上去更好看一点。一般较短的形式比较长的形式更容易阅读一些。然而,过度简洁也会导致代码难以阅读。当我还是一个系统编程研究生班级的助教时,一位学生让我看一些代码,他说代码里存在一个未知的BUG,但是由于代码过于紧张,所以无法把它找出来。在一些高年级的C程序员的嘲笑中,我们系统的把类似下面的单行代码:
frotz[ --j + i++ ] += --y;
扩展为功能相同但长度更长的:
--y;
--j;
frotz[ j + i ] = frotz[ j + i ] + y;
i++;
这让那位喜爱玩弄技巧的程序员颇感懊恼,使用这种方法,我们一下子就发现其中一个操作位置有误。
教训:不要在一行代码里实现太多的功能。
这种做法并不能使编译器产生的代码更有效率,而且会使你丧失调试代码的机会。正如K&R 所指出的那样,人人都知道调试比第一次编写代码要难上一倍。所以如果在编写代码时把自己的聪明发挥到极致,那么在调试时怎么办?