概述

副作用

《C语言核心技术》对副作用的描述:

表达式内包含了一串的常量、标识符、运算符(指示的运算方式)。表达式的目的可以是获得结果值,或者得到运算的副作用(side effect),或者两者兼备。

为了说明这句话,我们需要举几个表达式的例子:

示例1:

int x = ;
x + ;

表达式x + 1就产生了一个值,但是它没有产生一个副作用。

示例2:

int x = ;
x = x + 3;

表达式x = x+ 3产生了一个值,同时也会产生一个副作用。

以上两个例子在现阶段,你可能并不容易理解它产生的值或副作用是什么意思,但是结合《C语言核心技术》对副作用概念的解析,你应该能够明白:

一个表达式的运行,在产生一个值的过程中,表达式可能会对环境做出其他的改变,这样的改变被称为副作用(side effect),诸如变量的值被修改,或者输入输出流的数据有所变化。

回头看看示例1的表达式x + 1,由于它没有对环境做出诸如改变变量的操作,所以它的作用只是产生了一个值,这个值是3.

而示例2的表达式x = x + 3,它不但产生一个值(5),同时还产生了一个副作用,这个副作用是改变变量x的值为5,在遇到一个序列点之前,它会完成这个操作

现在我们引入了一个新的概念,序列点

在程序的执行旗舰有一些点,在这些点中,一个特定表达式的所有副作用都会完成,而下一个表达式的副作用尚未发生。程序中这样的点被称为序列点。在两个连续的序列点之间,可以用任何次序做局部运算。作为一名程序员,你必须特别小心,不要在两个连续的序列点之间多次修改任何对象。

通俗点说就是,当表达式的执行遇到一个序列点的时候,它前面所产生的副作用都会被完成,才会继续执行下去。

然而如果当前产生了几个以上的副作用,而这个副作用又是同时作用于同一个对象(一般是修改),那么它的执行结果是不确定的!

除了以上情况,还有一点,就是如果一个表达式在两个序列点之间调用了函数,这个函数的运行顺序和自变量表达式的运行顺序也是不确定的!

在《C语言核心技术》第5章:"函数调用"一小结中提到:

至于程序是以怎样的次序来计算“函数表达式”和个别“自变量表达式”,这是没有定义的。

请看示例3:

int i = ;
printf( "%d %d\n", i, ++i ); //行为没有定义

printf中的两个自变量表达式当中,子表达式i和++i是不能确定运行顺序的,因为++i有一个副作用,如果这个副作用先运行,那么表达式i的结果就是1,否则i的结果就是0;

除了自变量表达式的执行顺序,函数本身的执行顺序也是不确定的,如果一个两个序列点之间的表达式中出现了多次函数调用的话!

请看示例4:

int x = f() + g();

该表达式产生了一个值(f() + g()),产生了3个副作用:

  • 修改x的对象为表达式结果;
  • 运行函数f;
  • 运行函数g;

如果函数f和函数g之间的执行绪互相不影响,那么它的结果是没有问题的,因为在遇到序列点之前,虽然产生了3个副作用,但是x依赖子表达式f() + g()的值,所以x的值是不需要担心的,它必然是子表达式(f() + g())的值。

然而在子表达式中,函数f和函数g之间的运行顺序是不确定的,有些编译器编译下,可能先运行f(),有些可能先运行g()!

请看示例5:

int x = ;
x = x++;

在第二条表达式中,一共产生了两个副作用:

  • x++得出了一个值,留下一个副作用:把x的值赋值为2(因为是左递增,所以这里表达式得出来的值是x递增之后的值,2);
  • 而前面的赋值操作也留下了一个副作用:把x赋值为子表达式x++得出来的值1(因为是右递增,所以这里表达式得出来的值是x递增之前的值,1),这个副作用一旦执行,就会把变量x赋值为1;

然后呢,如果第一个副作用先执行,x++先把x的值改变为2,然后轮到第二个副作用登场了,没有错,它把x的值又赋值为1了。。。

在这次操作中,x++的递增1运算就相当于丢失了,如果不考虑序列点,表达式的运算结果就是不可预知的。

所以我们必须确保在两个序列点之间的代码,不会出现修改同一个对象多次的副作用出现。

会出现序列点的位置

  • 在一个函数调用时,所有的自变量被计算之后,并且在执行权传递到函数语句之前。
  • 在表达式的末端,并且此表达式不是一个更大的表达式的一部分。这种完整的表达式包括:“表达式语句”内的表达式(请参考第6章“表达式语句”),for语句内的三个条件表达式、if或while语句的条件语句、return语句的表达式,以及初始化语句(initializer)。
  • 在下列运算符的第一个操作数被计算完成后:
  1. && (逻辑 AND)
  2. || (逻辑 OR)
  3. ? : (条件运算符)
  4. , (逗号运算符)

示例6:

++i <  ? f(i++) : (i = );

这个表达式是合适的,因为在第一个修改i的地方和另外两个修改i的地方之间有一个序列点。

[C]副作用和序列点的更多相关文章

  1. C语言中的副作用、序列点、完整表达式

    C语言中有个术语叫:副作用 副作用其实是对数据对象或文件的修改.(数据对象的定义是:用于存储值的数据存储区域) 例如语句 states = 50; 从C语言的角度来讲:这个赋值表达式的副作用是将变量的 ...

  2. C语言的本质(8)——副作用与顺序点

    C 语言中,术语副作用是指对数据对象或者文件的修改.例如以下语句 var = 99; 的副作用是把 var 的值修改成 99.对表达式求值也可能产生副作用,例如: se = 100 对这个表达式求值所 ...

  3. C语言序列点问题总结(大多数高等教育C语言教学课程的漏洞)

    C语言序列点总结 2013年11月21于浙大华家池 C 语言副作用: (side effect)是指对数据对象或者文件的修改. 例如,语句 v = 99;的副作用是把 v 的值修改成 99. C语言序 ...

  4. HTTP超文本传输协议-HTTP/1.1中文版

    摘要 超文本传输协议(HTTP)是一种为分布式,合作式,多媒体信息系统服务,面向应用层的协议.它是一种通用的,不分状态(stateless)的协议,除了诸如名称服务和分布对象管理系统之类的超文本用途外 ...

  5. 【C】 04 - 表达式和语句

    程序的生命力体现在它千变万化的行为,而再复杂的系统都是由最基本的语句组成的.C语句形式简单自由,但功能强大.从规范的角度学习C语法,一切显得简单而透彻,无需困扰于各种奇怪的语法. 1. 表达式(exp ...

  6. HTTP/1.1协议(中文归纳版)

    一.介绍(introduction) 1. 目的——HTTP/0.9-〉HTTP/1.0-〉HTTP/1.1 2. 要求——MUST.REQUIRED.SHOULD 3. 术语——连接(Connect ...

  7. 超文本传输​​协议 - HTTP / 1.1(Hypertext Transfer Protocol -- HTTP/1.1)之方法定义(Method Definitions)

    9方法定义 下面定义了HTTP / 1.1的一组常用方法.尽管可以扩展这个集合,但是另外的方法不能假定为单独扩展的客户端和服务器共享相同的语义. 主机请求头域(14.23节)必须伴随所有的HTTP / ...

  8. RFC2616-HTTP1.1-Methods(方法规定部分—译文)

    part of Hypertext Transfer Protocol -- HTTP/1.1RFC 2616 Fielding, et al. 9 方法定义 下面列出了有关HTTP/1.1协议的一些 ...

  9. C Primer Plus学习笔记(四)- 运算符、表达式和语句

    基本运算符 赋值运算符:= 在C语言中,=不是“相等”,而是赋值运算符,把左边的值赋给右边的变量 a = 2018; //把值2018赋给变量a 赋值表达式语句的目的是把值储存到内存位置上,用于储存值 ...

随机推荐

  1. Where/Order by/Ggroup by/Having使用的注意事项

    1.Where.Order by.Group by .having Where作用对象是:基本表或视图,从中选出符合条件的元素. Order by 作用对象是:基本表或视图,就是排序方式,分为升序(A ...

  2. 【CuteJavaScript】ES2019 新特性汇总

    最近 ECMAScript2019,最新提案完成:tc39 Finished Proposals,我这里也是按照官方介绍的顺序进行整理,如有疑问,可以查看官方介绍啦~ 另外之前也整理了 <ES6 ...

  3. 201871010119-帖佼佼《面向对象程序设计(java)》第一周学习总结

    项目 内容 这个作业属于哪个课程 <任课教师博客主页链接>  https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 <作业链接地址>   ...

  4. 抓包工具Charles的使用说明

    Charles介绍&前期准备: Charles介绍:Charles跟fiddler是目前行业中比较常用的抓包工具,这里主要介绍Charles的使用,fiddler的使用类似. 前期准备:使用前 ...

  5. python并发之多进程

    #mutiprocessing模块 python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程.Pytho ...

  6. freemarker数据格式化问题(即数值超过三位后自动添加逗号问题)

    实际数据:{value:1007, name:'通用设备'}, 浏览器回显数据: 得出: freemarker 当数据超过3位的时候,会自动用逗号截取 格式如:1,007 解决办法: 加?c,如:${ ...

  7. tyvj 1387 迷你火车头

    dp百题进度条[1/100] 一列火车有一个火车头拖着一长串的车厢,每个车厢有若干个乘客. 一旦火车头出了故障,所有的车厢就只能停在铁轨上了,因此铁路局给每列火车配备了三个迷你火车头,每个迷你火车头可 ...

  8. 《漫画ERP》经典文章摘抄

    1.对企业来说,应用ERP的价值就在于通过系统的计划和控制功能,结合企业的流程优化,有效的配置各项资源,以加快对市场的响应,降低成本,提高效率和效益,从而提升企业的竞争力:

  9. 64-Weave 如何与外网通信?

    上一节我们学习了 Weave 网络内部如何通信,今天讨论 Weave 如何与外界通信. weave 是一个私有的 VxLAN 网络,默认与外部网络隔离.外部网络如何才能访问到 weave 中的容器呢? ...

  10. MPV源码探究:背景及准备工作

    背景及准备工作 一点历史 古有魏蜀吴三分天下,今有 Mplayer.MPC.VLC 三分天下.这个世界观太庞大,忍不住先讲一点多媒体播放器的历史.实际上目前市面上的开源播放器主要基于三种技术栈: Mp ...