Sequence Point 顺序点
1)在函数调用之前,对函数的所有参数的求值必须全部完成
2)一旦一个函数开始执行, 调用者函数中的表达式将不会开始求值,或继续求值,直至被调用函数执行结束,函数执行永远不会交叉进行.
3)如果函数参数是表达式,这些表达式通常可以按任何次序求值,包括交叉求值.
对于f(expr1, expr2) 编译器可能先对expr1 求值,也可能后对expr1求值,或者让expr1 和 expr2 的求值交叉进行
对于f(g(expr1),h(expr2)),expr1必须在g()被调用之前求值, expr2 必须在h()被调用前求值, g(),h()都必须在f()被调用之前执行完毕, expr1和 expr2 的求值可以交叉进行
一个顺序点,被定义为程序执行过程中的这样一个点:该点前的表达式的所有副作用,在程序执行到达该点之前发生完毕;该点后的表达式的所有副作用,在程序执行到该点时尚未发生。
(++i) + (++j)这个表达式本身不包含顺序点,所以i++,j++这两个“副作用”到底谁先发生,根据标准,是未定义的。如果给这个表达式加上顺序点,如:
;(++i) + (++j);
标准只保证,这两个副作用在整个表达式求值完成前(即到达后面的顺序点";"前)都会发生,并且不会在上一个语句执行完毕之前发生。
标准还规定,两个相邻顺序点之间,对某一表达式求值,最多只能造成任一特定对象的值被更改一次。如果表达式求值过程会更改某对象的值,那么要求更改前的值被读取的唯一目的,只能是用来确定要存入的新值。
例如下面的表达式,按照标准规定,执行结果是未定义的:
(i++)+(i++)
这个表达式本身不包含任何顺序点,但是对这个表达式求值,按照运算符定义,将更改i两次,违反了“一次更改”的要求。
再看下面的表达式,按照标准规定,执行结果也是未定义的:
x[i]=i++
这个表达式本身不包含任何顺序点,虽然i的值只更改了一次,但是x[i]这个左值中,i被读取,用于确定数组中被修改的元素的下标。这次对i求值和i++ 肯定位于同一对顺序点之间,该表达式求值过程更改了i的值,x[i]中读取i却不是为了确定i的新值,这违反了“读取只能用于确定新值”规定。
任何对相邻顺序点间表达式求值的多个副作用发生的顺序进行假设,或者违反上述“一次更改、读取仅用于确定新值”规定的代码,其执行结果都是未定义的。这里所说的“未定义”,通常比“不可移植”更严重,可以认为是“错误”的同意词。
通常我们认为,标准对“顺序点”及其语义的定义,是为了严谨地定义C/C++的表达式和求值过程,并不是为了让程序员通过对顺序点的掌握,(过分地)利用表达式求值的副作用。实际工作中,我们完全可以通过引入中间变量,避开“顺序点”这样容易出错,也极大地降低代码可读性的“边缘概念”。
sample
f(new T1, new T2)
编译器生成代码1:
1)为T1分配内存
2)构造T1
3)为T2分配内存
4)构造T2
5)调用f()
如果第三步,或第四步失败,C++标准不会要求T1对象被摧毁并释放内存。
编译器生成代码2
1)为T1分配内存
2)为T2分配内存
3)构造T1
4)构造T2
5)调用f()
如果第三步失败 T1内存会被释放, C++标准并没有要求释放T2内存, 如果第四步失败, T1已经被完全构造,但是C++标准并没有要求将其摧毁并释放内存。
正确方法:
auto_ptr<T1> t1(new T1);
auto_ptr<T2> t1(new T2);
f(t1,t2);
在一个表达式中,如果其他代码有可能跑出异常,就绝对不要在这个表达式中分配资源, 即使通过new获得的资源立即在同一表达式中被管理起来。