[C]副作用和序列点
Posted yiyide266
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C]副作用和序列点相关的知识,希望对你有一定的参考价值。
概述
副作用:
《C语言核心技术》对副作用的描述:
表达式内包含了一串的常量、标识符、运算符(指示的运算方式)。表达式的目的可以是获得结果值,或者得到运算的副作用(side effect),或者两者兼备。
为了说明这句话,我们需要举几个表达式的例子:
示例1:
int x = 2; x + 1;
表达式x + 1就产生了一个值,但是它没有产生一个副作用。
示例2:
int x = 2; x = x + 3;
表达式x = x+ 3产生了一个值,同时也会产生一个副作用。
以上两个例子在现阶段,你可能并不容易理解它产生的值或副作用是什么意思,但是结合《C语言核心技术》对副作用概念的解析,你应该能够明白:
一个表达式的运行,在产生一个值的过程中,表达式可能会对环境做出其他的改变,这样的改变被称为副作用(side effect),诸如变量的值被修改,或者输入输出流的数据有所变化。
回头看看示例1的表达式x + 1,由于它没有对环境做出诸如改变变量的操作,所以它的作用只是产生了一个值,这个值是3.
而示例2的表达式x = x + 3,它不但产生一个值(5),同时还产生了一个副作用,这个副作用是改变变量x的值为5,在遇到一个序列点之前,它会完成这个操作。
现在我们引入了一个新的概念,序列点:
在程序的执行旗舰有一些点,在这些点中,一个特定表达式的所有副作用都会完成,而下一个表达式的副作用尚未发生。程序中这样的点被称为序列点。在两个连续的序列点之间,可以用任何次序做局部运算。作为一名程序员,你必须特别小心,不要在两个连续的序列点之间多次修改任何对象。
通俗点说就是,当表达式的执行遇到一个序列点的时候,它前面所产生的副作用都会被完成,才会继续执行下去。
然而如果当前产生了几个以上的副作用,而这个副作用又是同时作用于同一个对象(一般是修改),那么它的执行结果是不确定的!
请看示例3:
int x = f() + g();
该表达式产生了一个值(f() + g()),产生了3个副作用:
- 修改x的对象为表达式结果;
- 运行函数f;
- 运行函数g;
如果函数f和函数g之间的执行绪互相不影响,那么它的结果是没有问题的,因为x依赖子表达式f() + g()的值。
然而函数f和函数g之间的运行顺序是不确定的,有些编译器编译下,可能先运行f(),有些可能先运行g()!
请看示例4:
int x = 1; x = x++;
在第二条表达式中,一共产生了两个副作用:
- x++得出了一个值,留下一个副作用:把x的值+1;
- 而前面的赋值操作也留下了一个副作用:把x赋值为子表达式x++(因为是右递增,所以这里表达式得出来的值是x递增之前的值,1)得出来的值1,这个副作用一旦执行,就会把变量x赋值为1;
然后呢,如果第一个副作用先执行,x++先把x的值改变为2,然后轮到第二个副作用登场了,没有错,它把x的值又赋值为1了。。。
在这次操作中,x++的递增1运算就相当于丢失了,如果不考虑序列点,表达式的运算结果就是不可预知的。
所以我们必须确保在两个序列点之间的代码,不会出现修改同一个对象多次的副作用出现。
会出现序列点的位置
- 在一个函数调用时,所有的自变量被计算之后,并且在执行权传递到函数语句之前。
- 在表达式的末端,并且此表达式不是一个更大的表达式的一部分。这种完整的表达式包括:“表达式语句”内的表达式(请参考第6章“表达式语句”),for语句内的三个条件表达式、if或while语句的条件语句、return语句的表达式,以及初始化语句(initializer)。
- 在下列运算符的第一个操作数被计算完成后:
- && (逻辑 AND)
- || (逻辑 OR)
- ? : (条件运算符)
- , (逗号运算符)
示例5:
++i < 100 ? f(i++) : (i = 0);
这个表达式是合适的,因为在第一个修改i的地方和另外两个修改i的地方之间有一个序列点。
以上是关于[C]副作用和序列点的主要内容,如果未能解决你的问题,请参考以下文章
为啥我的 C 代码片段不起作用?简化版可以。为 unsigned long long 传递不带 VA_ARGS 的 args