关于C/C++中的side effect(负效应)和sequence point(序列点)

所属分类: 软件编程 / C 语言 阅读数: 87
收藏 0 赞 0 分享

不知你在写code时是否遇到这样的问题?int i = 3; int x = (++i) + (++i) + (++i); 问x值为多少?进行各种理论分析,并在编译器上实践,然而可能发现最终的结果是不正确的,也是不稳定的,不同的编译器可能会产生不同的结果。这让人很头疼。结果到底是啥呢?对于此题的答案,一句话,Theresult is undefined! 详细解释待我慢慢说来。

大家知道,通常而言,我们写的计算机程序都是从上到下,从左到右依次执行。然而,我只是说通常,因为在编译的过程中,compiler并不仅仅是把source code翻译成binary code就算了,这个过程里面可能还会对代码进行优化,这种优化可能带来的结果是:代码或者表达式evaluation的顺序可能发生变化。这可是一个非常严重的问题,当某个表达式带有side-effect(比如改变了一个变量的值),那么它的执行顺序直接影响到了程序执行的结果。

为了保证程序执行具有确定性的结果,C++标准引入Sequence Point这个概念,按照ISO/IEC的定义:

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仍未开始,而两个Sequence Point之间所有的表达式或者代码执行的顺序是未定义的!

而C++标准又进一步规定了Sequence Point出现的5种情况:

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

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

3、After copying of a returned value and before execution of any expressions outside the function
函数即将返回时是Sequence Point,因为函数返回时必然会结束掉一个完整的表达式。

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

5、After the initialization of each base and member in the constructor initialization list
在一个完整的声明末尾是Sequence Point,所谓完整的声明是指这个声明不是另外一个声明的一部分。比如声明int a[10], b[20];,在a[10]末尾是Sequence Point,在b[20]末尾也是。

经过以上说明,大家已有所了解,现在回到我们的题目:int x = (++i) + (++i) + (++i); 整个的语句里面,只有1个Sequence Point,也就是语句的结束点,对于右边表达式的计算顺序没有任何的规定,显然,各种编译器都可以按照他们觉得“舒服”的方式来进行计算,这样的代码,如果只要求在特定的平台或者编译器运行,那么带来的可能只是可读性差的问题,但如果考虑跨平台或者编译器的情况,那么就是完完全全的错误!

另外,需要特别注意的是,对于赋值号(assignment operator),C++也没有把它定义成Sequence Point,也就说这样的语句:buffer[i] = i++;同样是undefined的,因为,对于等号左右两边的表达式运算顺序,你并不能有任何的假定。

更多精彩内容其他人还在看

C++中四种对象生存期和作用域以及static的用法总结分析

以下是对C++中四种对象生存期和作用域以及static的用法进行了详细的介绍,需要的朋友可以过来参考下
收藏 0 赞 0 分享

C++嵌套类与局部类详细解析

从作用域的角度看,嵌套类被隐藏在外围类之中,该类名只能在外围类中使用。如果在外围类之外的作用域使用该类名时,需要加名字限定
收藏 0 赞 0 分享

C++空类详解

以下是对C++中的空类进行了详细的介绍,需要的朋友可以过来参考下
收藏 0 赞 0 分享

C++之友元:友元函数和友元类详解

友元是一种允许非类成员函数访问类的非公有成员的一种机制。可以把一个函数指定为类的友元,也可以把整个类指定为另一个类的友元
收藏 0 赞 0 分享

C++中返回指向函数的指针示例

int (*ff(int)) (int *,int);表示:ff(int)是一个函数,带有一个int型的形参,该函数返回int (*) (int *,int),它是一个指向函数的指针,所指向的函数返回int型并带有两个分别是Int*和int型的形参
收藏 0 赞 0 分享

C数据结构之单链表详细示例分析

以下是对C语言中的单链表进行了详细的分析介绍,需要的朋友可以过来参考下
收藏 0 赞 0 分享

C数据结构之双链表详细示例分析

以下是对c语言中的双链表进行了详细的分析介绍,需要的朋友可以过来参考下
收藏 0 赞 0 分享

浅析如何在c语言中调用Linux脚本

如何在c语言中调用Linux脚本呢?下面小编就为大家详细的介绍一下吧!需要的朋友可以过来参考下
收藏 0 赞 0 分享

深入解析unsigned int 和 int

以下是对unsigned int和int进行了详细的分析介绍,需要的朋友可以过来参考下
收藏 0 赞 0 分享

浅谈C++中的string 类型占几个字节

本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节
收藏 0 赞 0 分享
查看更多