顺序点,也称作序列点,是电脑程序中一些执行点,在该点处之前的求值的所有的副作用已经发生,在它之后的求值的所有副作用仍未开始。在C与C++程式设计语言中,表达式的值依赖于它的子表达式的求值顺序。增加更多的顺序点限制了可能的求值顺序,能保证有一个一致结果。
C++11中,顺序点概念已经被sequenced before这种方法取代:直接指出一个求值是在另一个求值之前,或者两个求值是无顺序的。[1][2]无顺序的求值可以重叠进行。
歧义例子
考虑两个函数f()
与g()
。在C与C++中,加法运算符+
不是一个顺序点,因此表达式f() + g()
可能会先调用f()
,或先调用g()
。逗号运算符引入了一个顺序点,因此表达式f(), g()
的求值顺序是确定的:首先调用f()
,然后调用g()
。
当一个变量在一个表达式修改不止一次,顺序点也发挥作用。一个典型的C语言例子是表达式i = i++
,其中i
的结果值是有二义性的,依赖于表达式求值顺序:自增运算可能发生在赋值之前、之后或者交错进行。在C与C++语言中,这会导致未定义行为。[3]
C与C++的顺序点
在C[4]与C++[5]中,顺序点出现在下述位置:(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
,c
的求值顺序未指定。 - 函数返回时,在返回值已经复制到调用上下文。(仅C++标准指出这一顺序点[6])
- 初始化的结束。例如,声明
int a = 5;
中的对5
求值之后。 - 初始化列表的以逗号分割的各个初始化值,严格遵照从左至右求值。例如:
int a[3] = {i++,j--,foo(101)};
注意,此处不是逗号运算符。(从C++11标准指出这一顺序点) - 在声明序列的每个声明(declarator)之间。例如,
int x = a++, y = a++
的两次a++
求值之间。[7]注意,此例不是逗号运算符。