关于 i++ + ++i 是什么的终极解释
我知道大部分人都了解这个东西是未定义的. 但是反复在面试笔试中看到这种问题实在令人火大. 今天又看见这种题实在忍不住了. 如果是面试我还能跟对方解释, 如果笔试出这种题就只剩下我鄙视它了. 我发贴的目的只是想帮助一下苦命学生. 万一出题的老师或者面试官看见了这贴, 麻烦您顺手在选项中间加一个"不确定", 或者"gcc"吧.
下面根据规范说明这一点. 如果看过就请离开吧. 占用大家时间很不好意思.
C 语言:
C 语言使用 sequence point 来定义表达式的求值顺序. 在两个 sequence point 之间一个对象的 value 只能被修改至多一次(注意这里的语气非常强硬). 另外, 在先的值(这里指的是修改之前的值)如果被读, 则只能被用来决定应该写什么值. 这里所谓 sequence point, 指的是在表达式中特定的点, 在这个点上, 先前的副作用都已完成, 而之后的副作用都还没有发生. 所谓副作用是:
读写 volatile 对象;
修改一个对象;
函数调用;
修改一个文件.
另外, 副作用的顺序是未指定(未指定和未定义有点区别, 但总之都是和实现相关. 后文有时混用两者, 大家凑合着看吧)的.
C规范用一个 annex 总结了所有 sequence points:
1. 参数求值之后, 函数调用之前;
2. 以下操作符的第一个操作数结束之前: &&, ||, ?:, 逗号; (因为这一条, j = i++ && i++; 就是定义的, 而 j = i++ + i++ 则是未定义的, 如果我出题就考这么一个表达式: "i = -1, i++ + 1 || i++")
3. 一个完整的 declarator 之后;
4. 一个完整的表达式之后;
5. 库函数返回之前;
6. conversion specifier (就是 printf 里面 % 打头的那个东西) 对应的行为之后 (这并不意味着 i = 0, printf("%d, %d", i++, i++); 应该是 0, 1 (gcc 是 1, 0). 规范明确, 反复地说函数参数求值顺序是未指定的, 因此用那个 printf 考人也很混蛋, 除非他说明用什么编译器)
7. 提供给 bsearch 和 qsort 的比较函数被调用之前和之后, 以及调用比较函数和移动对象之间.
如此, 像这样的表达式:
(i++)+(i++)+(i++)和(++i)+(++i)+(++i); (http://www.javaeye.com/wiki/topic/512205)
b = a++ + a++ + a++; (http://blog.csdn.net/huanglaobo/archive/2009/04/20/4093123.aspx)
都是未定义的.
a[i++] = i 也是为定义的, 因为它读取 i 的值不只是为了确定应该 store 什么;
i = 0;
int * p = &i;
i = (*p)++;
是未指定的, 因为'对i赋值'和 '*p自增' 这两个副作用的顺序是未指定的.
在C++中, 由于引入了并行, 现在不再用 sequence points 来定义求值顺序, 而改用 happens before 关系了. 我相信这不会导致某个在 C 中未定义的东西在 C++ 中变成定义的了. 相反的应该也不会.
C++ 是这样说的:
如果一个标量的副作用和同一个标量的另一个副作用, 或者和一个使用这个标量值的计算, 没有定义顺序, 则行为是未定义的.
而规范中对每个算符的操作数于算符本身的计算是否有序进行单独的定义. 对于一般情况:
算符的运算对象的求值顺序, 或者表达式的子表达式的求值顺序是没有顺序(unsequenced), 除非规范中另有定义;
运算对象的求值 在 所在的算符的求值 之前 (sequence before);
逗号表达式左边的求值和副作用先于右边的;
对于赋值: 副作用发生在左右表达式求值之后, 整个赋值表达式求值之前. 但是左右表达式之间没有定义顺序.
对于后缀 ++ 和 --: 副作用发生在求值之后. 至于后到什么程度则没说. 因此在整个表达式结束时副作用也可以;
对于前缀 ++ 和 -- 则稍微复杂一点. 规范中只是说了它的语义, 并且说如果 x 不是 bool 类型, 则 ++x 等价于 x+=1 (而且根据规范, x 很快就不能是 bool 类型了). 至于 x+=1 则是一个赋值操作. 赋值操作的顺序是左右表达式-->副作用-->求值. 不过这并不意味着 i=1, j = ++i + ++i 就必须是 5. 实际上因为加法没有定义左右操作数求值顺序, 这里对 i 的两次副作用是无序的, 因此还是未定义.
现在看来唯一有希望有定义的这种表达式就是 i = ++i 了. 由于 ++i 等价于 i += 1, 是个赋值, 它的副作用需要在求值之前. 所以这次两个副作用是有序的. 可惜的是, 左边的 i 虽然是左值, 但是也是要通过求值求出来. 这回左边的求值和右边的副作用是无序的, 因此这个东西还是未定义的.
我承认, 我在这里喷了这么多, 主要原因还是为了发泄不满. 不过只要有一个原本不知道这些的出题人看见了这贴, 我就算造福了参加他考试的一众学生, 也算是积德行善吧. 谢了.