当程序执行序列执行到某些特定被称为序列点(sequence point)的时候,应该完成此前计算的所有副作用(side effect)同时不能发生任何子序列所产生的副作用(side effect).
1,什么是side effect
c99的定义为: Accessing a volatile object, modifying an object, modifying a file, or
calling a function that does any of those operations are all side effects,
which are changes in the state of the execution environment.
c++03的定义为: Accessing an object designated by a volatile lvalue, modifying an object,
calling a library I/O function, or calling a function that does any of
those operations are all side effects, which are changes in the state of
the execution environment.
从c99和c++03的定义来看:可以把程序想象成一个状态机,在任意时刻可以程序的状态包含了它的所拥有对象的值以及文件的内容,而对象的值的改变或者文件内容的改变会导致副作用也就是说副作用不是自己主动出现的(这一句肯定看的稀里糊涂的别着急往下看),因此导致程序状态的改变.
demo1 for no effect:
#include <iostream>
int main()
{
int i{ 0 };
i + 1;//编译时候编译器会警告:
//warning C4552: '+': operator has no effect; expected operator with side-effect
//由于这一句对程序变量的值并没有影响因此,编译器可以跳过不执行.
++i; //这一句改变了程序中变量的值因此对程序有一个side effect!
return 0;
}
由上面的demo1需要特别指出的是: c99和c++03都指出no effect的expression允许不被执行.
An actual implementation need not evaluate part of an expression if it
can deduce that its value is not used and that no needed side effects
are produced (including any caused by calling a function or accessing
a volatile object).
2, 什么是sequence point
c99和c++03对于序列点的定义相同: At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and
no side effects of subsequent evaluations shall have taken place.
中文意思是: 序列点(sequence point)是一些被特别规定的位置,要求程序运行到该序列点的时候所有发生在该序列点之前的对程序中变量值的改变或则文件内容的修改必须已完成!(也就是说由于变量的值的改变或者文件内容修改产生的side effect在此结束).同时要求下次一变量值的改变或者文件的修改产生的side effect还没开始.
demo1 for full expression:
extern int i;
extern int j;
i = 0;
j = i;
上面的代码中 i=0; 和 j=i; 都是完整的表达式(full expression). 其中 ; 说明了表达式的结束(在C/C++中 ; 都是序列点之一). 因此我们根据序列点的定义可以知道 i=0; 这个表达式一定在 ; 之前完成对 i 的赋值且由于改变 i 的值产生的side effect必须结束. 而改变 j 的值产生的副作用还没开始.
demo2 for 表达式求值与side effect发生的相互顺序:
c99和c++2003都规定:
Except where noted, the order of evaluation of operands of individual
operators and subexpressions of individual expressions, and the order
in which side effects take place, is unspecified.
中文意思是: C/C++在表达式求值的过程中 表达式中的操作数的求值(可以理解为表达式的子表达式)顺序是不确定的这也就是说产生的side effect顺序是不确定的(这会带来什么影响稍后再demo中说明).
C/C++为什么不强制子表达式求值顺序呢?由于C/C++都是追求效率的语言不规定这些是为了编译器有更大的优化空间.
#include <iostream>
int main()
{
int* ptr{ new int{0} };
int n{ 0 };
*ptr = n++; //A
delete ptr;
ptr = nullptr;
return 0;
}
上面的代码A处 不仅修改了ptr所指向的值而且修改了n的值产生了2次side effect,至于哪个先发生呢全看编译器提供商了因此最好不要这么写回头出了问题查都没地方查.
3, sequence point对side effect的限制:
c99和c++2003都有类似的如下规定
Between the previous and next sequence point a scalar object shall
have its stored value modified at most once by the evaluation of an
expression. Furthermore, the prior value shall be accessed only to
determine the value to be stored. The requirements of this paragraph
shall be met for each allowable ordering of the subexpressions of a
full expression; otherwise the behavior is undefined.
简单总结就是: 在两个序列点之间的一个变量只能被修改一次,否则就会产生undefined behavior.之所以会这样是因此C/C++没有规定子表达式的求值顺序.
demo1:
#include <iostream>
int main()
{
int x{ 0 };
int y{ 0 };
x = ++x + 1; //A
return 0;
}
demo1中的A处 获得的 x 的值是不确定的。该表达式对 x 所做的两次修改都要写入到 x 里面,x 的值取决于到底哪个值最后发生。 如果是赋值动作最后发生那么 x 的值为:3, 如果是 ++x 最后写回那么结果是2.
4, c99序列点:
— The call to a function, after the arguments have been evaluated.
— The end of the first operand of the following operators:
logical AND && ;
logical OR || ;
conditional ? ;
comma , .
— The end of a full declarator:
declarators;
— The end of a full expression:
an initializer;
the expression in an expression statement;
the controlling expression of a selection statement (if or switch);
the controlling expression of a while or do statement;
each of the expressions of a for statement;
the expression in a return statement.
— Immediately before a library function returns.
— After the actions associated with each formatted input/output function
conversion specifier.
— Immediately before and immediately after each call to a comparison
function, and also between any call to a comparison function and any
movement of the objects passed as arguments to that call.
除此之外 只是规定库函数返回之后有一个序列点,并没有规定普通函数返回之后
有一个序列点.
5, c++03序列点:
— The call to a function, after the arguments have been evaluated.
— The end of the first operand of the following operators:
logical AND && ;
logical OR || ;
conditional ? ;
comma , .
— The end of a full declarator:
declarators;
— The end of a full expression:
an initializer;
the expression in an expression statement;
the controlling expression of a selection statement (if or switch);
the controlling expression of a while or do statement;
each of the expressions of a for statement;
the expression in a return statement.
— Immediately before a library function returns.
— After the actions associated with each formatted input/output function
conversion specifier.
— Immediately before and immediately after each call to a comparison
function, and also between any call to a comparison function and any
movement of the objects passed as arguments to that call.
c++03特别指出,进入函数(function-entry)和退出函数
(function-exit)各有一个序列点,即拷贝一个函数的返回值之后同样存在一个
序列点 .此外由于 &&, || 是可以重载的 因此如果是重载的这些运算符是等同于函数的有两个序列点.