#include <stdio.h> | |
int main() | |
{ | |
int a=5,b=5,c,d; | |
c=(a++)+(a++)+(a++); // 此语句在不同的编译器结果可能不一样 | |
d=(++b)+(++b)+(++b); // 此语句在不同的编译器结果可能不一样 | |
printf("%d,%d,%d,%d",a,b,c,d); | |
return 0; | |
} |
Vc 中结果为 8,8,18,22; VS 中为 8,8,15,24。
原因:VS 中,把括号里的内容先执行,既 b 自增三次以后再相加的;而对 a,a 表示的是运算了之后,再自加 1
所以在给 C 赋值的时候,a 的值还是 5,而 a 运算之后自加了三次,所以 a 的值为 8。
Vc 中,先按照自左向右的规则,先计算第一个 + 运算符,用第一个括号执行完就是 6,第二个执行完就是 7,第三个就是 8,算加号前,先算两边括号。就是算第一个加号时,两个 b 都被先执行了,b 就变成 7 了,所以 (++b)+(++b),两个 b 先自加,b=7,再加起来就是 14,然后再计算第二个加号,就是 14+(++b),一样 b 先自加,就是 8,14+8=22,就是这样来的;a 同理。
总结:编程时,应避免在一条语句中出现多次自加或者自减的操作。因为可能因为不同的环境(编译器)导致不同的结果,造成程序的不确定性。
**为什么(i++ + ++i)之类的代码是没有意义的**
很多程序员在学习或者面试的时候可能会遇到类似于 (i++ + ++i) 这样让人无奈的问题。其实像这样的问题根本没有必要纠结。
ISO C++ 2003: 5.0.4 摘录如下:
Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored.
The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined.
[Example: | |
i = v[i++]; // the behavior is unspecified | |
i = 7, i++, i++; // i becomes 9 | |
i = ++i + 1; // the behavior is unspecified | |
i = i + 1; // the value of i is incremented | |
—end example] |
在 ANSI/ISO C 标准这样描述:在上一个和下一个序列点之间,一个对象所保存的值至多只能被表达式的计算修改一次。而且前一个值只能用于决定将要保存的值。
序列点是一个时间点(在整个表达式全部计算完毕之后或在 ||、 &&、 ? : 或逗号运算符处,或在函数调用之前), 此刻尘埃落定,所有的副作用都已确保结束。
第二句话比较费解。它说在一个表达式中如果某个对象需要写入,则在同一表达式中对该对象的访问应该只局限于直接用于计算将要写入的值。这条规则有效地限制了只有能确保在修改之前才访问变量的表达式为合法。例如 i = i+1 合法,而 a [i] = i++ 则非法。
在许多经典书籍里也有类似情况的说明。
包含多个不确定的副作用的代码的行为总是被认为未定义
—— 引于《C 语言常见问题集》
简单言之,"多个不确定副作用" 是指在同一个表达式中使用导致同一对象修改两次或修改以后又被引用的自增、自减和赋值操作符的任何组合。
其实甚至都不要试图探究这些东西在你的编译器中是如何实现的(这与许多 C 教科书上的弱智练习正好相反);正如 K&R 明智地指出,"如果你不知道它们在不同的机器上如何实现,这样的无知可能恰恰会有助于保护你。"