Side Effect

改变计算机存储单元里的数据或者做输入或输出操作,这些都算Side Effect。

比如:

     int a = 10; /* a这个表达式在这里没有副作用,这里只是想要取得a这个变量的值10 */

     int b = a; /*,而b = a这个表达式有副作用,它的副作用是使b的值改变成a的值*/

正是因为有了副作用,很多功能才得以完成。有些表达式既会产生一个值,也会产生副作用。如i++这个表达式既会产生一个值(它是i自增以前的值),也会产生副作用。

Sequence Point

Sequence Point出现的5种情况:

(1)的表达式末尾是Sequence Point,所谓完整的表达式是指这个表达式不是另外一个表达式的一部分。所以如果有f(); g();这样两条语句,f()和g()是两个完整的表达式,f()的Side Effect必定在g()之前发生。

(2)调用一个函数时,在所有准备工作做完之后、函数调用开始之前是Sequence Point。比如调用foo(f(), g())时,foo、f()、g()这三个表达式哪个先求值哪个后求值是Unspecified,但是必须都求值完了才能做最后的函数调用,所以f()和g()的Side Effect按什么顺序发生不一定,但必定在这些Side Effect全部作用完之后才开始调用foo函数。

(3) 函数即将返回时是Sequence Point,因为函数返回时必然会结束掉一个完整的表达式。

(4) 条件运算符?:、逗号运算符、逻辑与&&、逻辑或||的第一个操作数求值之后是Sequence Point。如条件运算符和逗号运算符,条件运算符要根据表达式1的值是否为真决定下一步求表达式2还是表达式3的值,如果决定求表达式2的值,表达式3就不会被求值了,反之也一样,逗号运算符也是这样,表达式1求值结束才继续求表达式2的值。

(5) 在一个完整的声明末尾是Sequence Point,所谓完整的声明是指这个声明不是另外一个声明的一部分。比如声明int a[10], b[20];,在a[10]末尾是Sequence Point,在b[20]末尾也是。

Sequence Point就是这么一个位置,在它之前所有的side effect已经发生,在它之后的所有side effect仍未开始,而两个Sequence Point之间所有的表达式或者代码执行的顺序是未定义的!

a = (++a)+(++a)+(++a)+(++a);的结果之所以Undefined,是因为在这个表达式中对变量a的Side Effect有五次,这些Side Effect何时发生、按什么顺序发生是不一定的,只知道在整个表达式结束时一定都发生了,但在计算过程中要用到a的值时,能取出什么值就不确定了。这行代码用不同平台的不同编译器来编译,结果是不同的,甚至在同一平台上用同一编译器的不同版本来编译也可能不同。

写表达式应遵循的原则一: 在两个Sequence Point之间,同一个变量的值只允许被改变一次。仅有这一条原则还不够,例如a[i++] = i;的变量i只改变了一次,但结果仍是Undefined,因为等号左边改i的值,等号右边读i的值,到底是先改还是先读?这个读写顺序是不确定的。但为什么i = i + 1;就没有歧义呢?虽然也是等号左边改i的值,等号右边读i的值,但你不读出i的值就没法计算i + 1,那拿什么去改i的值呢?所以这个读写顺序是确定的。所以,写表达式应遵循的原则二: 如果在两个Sequence Point之间既要读一个变量的值又要改它的值,只有在读写顺序确定的情况下才可以这么写。