顺序点,也称作序列点,是计算机程序中一些执行点,在该点处之前的求值的所有的副作用已经发生,在它之后的求值的所有副作用仍未开始。在C与C++程序设计语言中,表达式的值依赖于它的子表达式的求值顺序。增加更多的顺序点限制了可能的求值顺序,能保证有一个一致结果。
简介在C、C++等允许表达式有副作用的语言中,通常都规定了顺序点,顺序点就是表达式中的副作用的最晚生效时刻,或指程序执行中关键的一点。在这个顺序点之前,所有的副作用的计算工作都必须完成,可以说顺序点是前一阶段计算的分水岭1。在程序执行中,可以存在一系列的顺序点,一旦执行到一个顺序点,此前的副作用都必须生效。但是在顺序点之间并没有任何保证。在C++11中,顺序点概念已经被这种方法取代:直接指出一个求值是在另一个求值之前,或者两个求值是无顺序的。无顺序的求值可以重叠进行。
表达式表达式亦称表示式、运算式或数学表达式,在数学领域中是一些符号依据上下文的规则,有限而定义良好的组合。数学符号可用于标定数字(常量)、变量、操作、函数、括号、标点符号和分组,帮助确定操作顺序以及有其它考量的逻辑语法。表达式有两种功能:
表达式一定会产生一个值
表达式可能会产生“副作用”(side effect)。
比如,i++的返回值是i,副作用是导致i增加1。
比如,++i的返回值是i+1,副作用也是导致i增加1。
表达式中的副作用何时生效以及运算对象的求值顺序,会影响到表达式的值。比如:
int a = 4, b;b = a++ + a;如果“+”的两个运算对象,从左到右计算:
如果后缀递增运算符的副作用,在计算第二个操作数“a”时,已经生效,那么b的值是9;
如果后缀递增运算符的副作用,在进入到“+”运算之时,才生效,那么b的值是8。
如果“+”的两个运算对象,从右到左计算,b的值都是8。
副作用在C++标准指出,副作用是访问一个由可变的左值(volatile lvalue)指派的对象(basic.lval),修改一个对象,调用库I/O函数,或者调用函数等所有这些能够改变执行环境的状态的操作都是副作用。
顺序点的位置在C与C++中,顺序点在下述位置出现:(C++的重载操作符的行为类似于函数)
&&(逻辑与)、||(逻辑或)、逗号运算符的左操作数与右操作数求值之间(前两者是短路求值的一部分)。例如,表达式*p++ != 0 && *q++ != 0,子表达式*p++ != 0的副作用都会在试图访问q之前完成。
三元条件运算符的第一个操作数之后,第二或第三操作数之前。例如,表达式a = (*p++) ? (*p++) : 0在第一个*p++之后存在顺序点,因而在第二个*p++求值之前已经做完一次自增。
完整表达式结束处。包括表达式语句(如赋值a = b;),返回语句,if、switch、while、do-while语句的控制表达式,for语句的3个表达式。
函数调用时的函数入口点。函数实参的求值顺序未指定,但顺序点意味着这些实参求值的副作用在进入函数时都已经完成。表达式f(i++) + g(j++) + h(k++),调用f(), g(), h()的顺序未指定,i, j, k的自增顺序也未指定。函数调用f(a,b,c)的实参列表不是逗号运算符,a, b, and c的求值顺序未指定。
函数返回时,在返回值已经复制到调用上下文。(仅C++标准指出这一顺序点[6])
初始化的结束。例如,声明int a = 5;中的对5求值之后。
初始化列表的以逗号分割的各个初始化值,严格遵照从左至右求值。例如:int a[3] = {i++,j--,foo(101)};注意,此处不是逗号运算符。(从C++11标准指出这一顺序点)
在声明序列的每个声明(declarator)之间。例如,int x = a++, y = a++的两次a++求值之间。[7]注意,此例不是逗号运算符。
本词条内容贡献者为:
李岳阳 - 副教授 - 江南大学