i++ : 顺序点(sequence point) 与 副作用 (side effect)(转)

声明:文章转自  http://www.cnblogs.com/smwikipedia/articles/1229984.html

表达式的计算分为两种,一种是有副作用的计算,如: 

(++x)+y 
一种是无副作用的计算,如: 
x*y 

有副作用的计算中,子表达式的计算顺序是重要的。例如 
(++x)*(x+1) 
当x=0时,如果先算++x,上式计算结果为2,如果先算x+1,上式计算结果为1。 
再如,对函数g(int, int)的调用g(x, ++x), 当x=1,这个调用是g(1, 2)还是g(2, 2)? 

所谓“顺序点”,和表达式的副作用紧密相关。再看这个例子: 

(++i) + (++j) 

这个表达式的计算,有两个副作用: 
i自增1; 
j自增1; 
但是到底哪一个先发生?答案是:任何答案都不对。 

为什么?因为标准并不定义副作用发生的顺序。标准只保证,一个表达式的全部副作用,不在达到该表达式紧邻的前一顺序点前发生,并且一定在达到该表达式紧邻的下一个顺序点之前发生完毕。 

一个顺序点,被定义为程序执行过程中的这样一个点:该点前的表达式的所有副作用,在程序执行到达该点之前发生完毕;该点后的表达式的所有副作用,在程序执行到该点时尚未发生。 

(++i) + (++j)这个表达式本身不包含顺序点,所以i++,j++这两个“副作用”到底谁先发生,根据标准,是未定义的。如果给这个表达式加上顺序点,如: 
;(++i) + (++j); 
标准只保证,这两个副作用在整个表达式求值完成前(即到达后面的顺序点";"前)都会发生,并且不会在上一个语句执行完毕之前发生。 

标准还规定,两个相邻顺序点之间,对某一表达式求值,最多只能造成任一特定对象的值被更改一次。如果表达式求值过程会更改某对象的值,那么要求更改前的值被读取的唯一目的,只能是用来确定要存入的新值。 
例如下面的表达式,按照标准规定,执行结果是未定义的: 
(i++)+(i++) 
这个表达式本身不包含任何顺序点,但是对这个表达式求值,按照运算符定义,将更改i两次,违反了“一次更改”的要求。 
再看下面的表达式,按照标准规定,执行结果也是未定义的: 
x=i++ 
这个表达式本身不包含任何顺序点,虽然i的值只更改了一次,但是x这个左值中,i被读取,用于确定数组中被修改的元素的下标。这次对i求值和i++肯定位于同一对顺序点之间,该表达式求值过程更改了i的值,x中读取i却不是为了确定i的新值,这违反了“读取只能用于确定新值”规定。 

任何对相邻顺序点间表达式求值的多个副作用发生的顺序进行假设,或者违反上述“一次更改、读取仅用于确定新值”规定的代码,其执行结果都是未定义的。这里所说的“未定义”,通常比“不可移植”更严重,可以认为是“错误”的同意词。 

通常我们认为,标准对“顺序点”及其语义的定义,是为了严谨地定义C/C++的表达式和求值过程,并不是为了让程序员通过对顺序点的掌握,(过分 地)利用表达式求值的副作用。实际工作中,我们完全可以通过引入中间变量,避开“顺序点”这样容易出错,也极大地降低代码可读性的“边缘概念”。



关于 “未定义” (undefined),参考:http://en.wikipedia.org/wiki/Undefined_behavior

关于 “顺序点”(sequence point),参考:http://en.wikipedia.org/wiki/Sequence_point
<think>好的,用户想了解C语言中的副作用side effect)概念。首先,我需要回忆相关的知识,并确保准确无误。根据引用[2]和[3],副作用指的是对数据对象或文件的修改,而序列则是程序执行中的一个关键,确保之前的副作用都已生效,后续的还未发生。 用户可能刚开始学习C语言,对副作用这个术语不太熟悉,需要通俗易懂的解释。我应该先定义副作用,然后举几个例子帮助理解。例如,赋值运算符、递增递减运算符都会产生副作用,因为它们修改变量的值。同时,函数调用也可能有副作用,比如修改全局变量或静态变量。 要注意区分表达式的主要目的和副作用。比如,表达式a = 5的主要目的是求值得到5,而副作用是将5赋给a。这一引用[2]已经提到,需要强调。 另外,需要解释序列的概念,因为它副作用密切相关。常见的序列包括分号、逗号运算符、函数调用等。在序列之前,所有的副作用必须完成,这对理解表达式求值顺序很重要。例如,i++ + i这样的表达式可能因为序列的问题导致未定义行为,这里可以作为例子说明。 用户可能还会想知道副作用的重要性,比如在程序调试中,副作用可能导致意料之外的结果,特别是在复杂的表达式中。这时候引用[3]的内容可以作为支持。 最后,是否需要补充一些实际应用中的注意事项?比如,避免在同一个表达式中多次修改同一个变量,以防止未定义行为。同时,可以提醒用户在设计程序时,尽量明确副作用的位置,增强代码的可读性和可维护性。 总结回答结构:先定义副作用,再举例子,解释序列,说明注意事项,最后总结重要性。需要确保引用正确的文献来源,比如引用[2]和[3]来支持解释。</think>### C语言中“副作用”的概念解释 在C语言中,**副作用Side Effect)** 是指表达式求值过程中对程序状态(如变量、内存或文件)的修改[^3]。简单来说,当一个表达式不仅计算值,还改变了某些数据对象或外部环境的状态时,就产生了副作用。 #### 1. 副作用的常见形式 - **赋值操作**:例如 `a = 5` 的主要目的是对表达式求值(结果为5),副作用是将变量 `a` 的值修改为5[^2]。 - **递增/递减运算符**:如 `i++` 的副作用是使 `i` 的值增加1。 - **函数调用**:若函数内部修改了全局变量或静态变量,调用该函数也会产生副作用。 #### 2. 副作用序列的关系 **序列Sequence Point)** 是程序执行中的一个时间,在此之前所有副作用必须完成,之后的副作用尚未发生。例如: - 分号 `;`(语句结束) - 逗号运算符(`,` 在特定上下文中) - 函数调用前的参数求值完成时 若违反序列规则,可能导致未定义行为。例如: ```c int i = 0; int x = i++ + i; // 未定义行为:i的修改和读取之间无序列 ``` #### 3. 注意事项 - **避免复杂表达式中的副作用**:如 `a = (b = 3) + (c = 4)` 虽然合法,但会降低可读性。 - **明确副作用的位置**:在涉及多个副作用的表达式中,需确保操作顺序符合预期。 #### 总结 副作用是C语言中直接影响程序状态的操作,需结合序列规则理解其执行顺序。合理控制副作用能提升代码的可维护性,滥用则可能导致难以调试的错误[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值