顺序点,C语言冰山位于海面下的角落。 副作用side effect,简单理解就是变量值被改变这个事实。i++这个表达式,副作用就是i被增加1。a = 10;这个表达式,副作用就是a被赋值成10。 一个复杂的表达式,其求值过程是有迹可循的。例如:i++ + ++i 这个表达式,根据运算符的结合性或优先级关系,可以被人为是如下等效形式:(i++) + (++i).现在的问题来了,它的求值结果是什么?答案是:未定义行为(undefined)。未定义的意思是:无法预测结果。对比另一个叫做为未指定行为(unspecified) ,说的是有几个结果,发生哪一个不知道。显然,未定义是更危险的。 这里面涉及到顺序点这个事。具体细节参考sequence point ? Eternity
简单的说,就是在顺序点之前,何时完成副作用,是不确定的事,不能根据子表达式,就说在此子表达式完成副作用。
顺序点是语法解析过程,对指定的几个词法位置的特殊标记。就好比道路上的路牌作用。 常用的顺序点(我用@符号表示这个路牌): 1)? 分号位置。 ? a = 10 ;@? 2)? 逗号表达式位置 ? a = 10 ,@? i++ ,@ a=i ;@ 3)? 三目运算符 ? a>0 ?@ i++ :@ j++ ;@ 4)? ......
副作用被限制在顺序点之间的任意位置发生。即两个@路牌之间发生。 回到?(i++) + (++i)这个面试和考试常考的地方,因为+号不是顺序点,所以,不能断言出i++的副作用在+之前完成。
可以想象成i++和++i的副作用是多线程方式(parallel)方式进行的。这是个多线程写。 另一个例子是a[i] = i++;因为[]和=都不是合规的顺序点,因此将发生多线程的读写。因此上述两个例子的结果都是不确定的。
实际上没有多线程,只是想象,帮助你理解为什么不能那么写程序,也不能拿这种例子考试别人。 只是编译器厂家可以做生成的指令重排列做效率优化(只要保证在顺序点之前实现副作用)。重排列指令顺序后,等效于多线程(单核)指令交替执行的效果。
下面是各个编译器上机实验:
#include <iostream>
int main()
{
int i = 1;
int a = i++ + ++i;
std::cout << a << std::endl;
return 0;
}
GCC: 提示 undefined behavior。不是说未定义变量i。
?CLANG:提示的更清楚,多个写操作发生了。
?VS2005:未提示 ?
|