表达式运算顺序与求值顺序,副作用操作符(++,--),序列点

来源:互联网 发布:内蒙古电信云计算 编辑:程序博客网 时间:2024/06/09 22:23

 

操作符(运算符)的优先级和结合性并不决定表达式的求值顺序,只是用于进行语法分析,决定语法树的生成。

3 + 4 * 5,可以解析成(3 + 4) * 5 或者 3 + (4 * 5),因为乘法优先级高于加法,所以会选择第二种解析方法。
3 + 4 + 5,可以解析成(3 + 4) + 5 或者 3 + (4 + 5),因为加法是自左向右结合,所以会选择第一种解析方法。

解析成3 + (4 * 5)后,对这个表达式求值,“+”操作符有两个操作数,左操作数为 3,右操作数为 4 * 5,那么是先对左操作数 3 求值呢,还是先对右操作数 4 * 5 求值呢?答案是未指定的,由编译器决定。

标准并没有指定“+”操作符的求值顺序,标准只指定了“,”、“&&”、“||”、“?:”这四种操作符的求值顺序是自左向右,其余的操作符的求值顺序都是未指定的。

因此 3 + (4 * 5),并非先算4 * 5,然后算 3 ,而是由编译器自己决定,求值顺序是未指定的。同理:
int i = f() + g(); // 并非一定先执行f(),后执行g(),而是由编译器决定

所以 ++i + ++i + ++i; 会被解析成 ((++i) + (++i)) + (++i);

这个表达式是一个典型的未定义行为,因为 ++i 有两个作用,第一:对i加1;第二:返回一个值(或者应该说返回一个引用)。这里的对i加1属于副作用(side effect),而副作用什么时候生效呢?标准只规定了在跨越一个序列点的时候,序列点之前的副作用必须全部完成,此外就没有硬性要求了。
 前置++
{
   this -> m_value += 1 ;
   return   *   this ;
 }
 后置++
{
   Int old  =   * this ;
   ++ ( * this );
   return  old;
}

序列点是一个时间点(在整个表达式全部计算完毕之后或在||、&&、? : 或逗号运算符处, 或在函数调用之前), 此刻尘埃落定, 所有的副作用都已确保结束。
C中为什么要搞这么一个复杂的序列点机制,让许多看起来很简单的代码变成未定义呢?为什么不详细的定义求值顺序呢?因为C是极端追求效率的语言,它诞生的年代计算机硬件都慢的可怜且贵的离谱,没有规定求值顺序就是要给编译器更大的余地去做优化。

而((++i) + (++i)) + (++i); 这个表达式的序列点就是语句的结束之处,也就是";"所在之处,在此之前副作用何时生效(也就是何时给i加1)完全由编译器决定,编译器可以任意选择,比如:

a, 先计算左边的子表达式((++i) + (++i)):
  先对左边的i加1(此时i == 6),再对右边的i加1(i == 7),然后再对左边的表达式求值,得到7,对右边的表达式求值,得到7,对子表达式求值,得到 7 + 7 = 14;
b,再计算右边的子表达式 (++i)
  因为此时i == 7,所以++i == 8,子表达式值为8.
c, 计算整个表达式的值:14 + 8 = 22;

编译器还可以选择先对所有的3个i都加1(完成副作用),然后再分别求值,得到8 + 8 + 8 = 24.

当然也可以挨个加1、求值,得到6 + 7 + 8 = 21.

只要满足在((++i) + (++i)) + (++i);结束之后,i == 8(副作用完成)就行了,具体过程标准并没有指定,由编译器自己决定。

至于说这种行为属于未定义,是因为c标准中有这么一条:

在上一个和下一个序列点之间, 一个对象所保存的值至多只能被表达式的计算修改一次。而且前一个值只能用于决定将要保存的值。

而这里的i显然在一条语句中被修改了多次。
对于 x=5,y=(++x)+(++x)+(++x),y=?
这是和编译器有关的具体如下,仅供参考:(只列举bc和vc的情况,其他的自己找规律)1、对于赋值计算,如: i = 5; k = (++i) + (i++) + (++i) + (++i);

  (1) BC下:  

  总体原则: 先计算所有的前置++或--,再计算表达式的值,最后计算后置++或--。

  a. 先计算所有i的前置++或--,得出i的值为8;

  b. 再计算k = 8 + 8 + 8 + 8 = 32;

  c. 最后计算i的后置++或--,得i的值为9。

  (2) VC下:

  总体原则:先计算前两项的前置++或--,再从第三项开始从左到右一项一项的计算,最后计算所

  有的后置++或--。

  a. 先计算前两项i的前置++或--,i为6,前两项之和为:6 + 6 = 12;

  b. 再计算第三项++i,i的值为7,前三项之和为:6 + 6 + 7 = 19;

  c. 再计算第四项++i,i的值为8,前四项之和为:k = 6 + 6 + 7 + 8 = 27;

  d. 最后计算i的后置++或--,得i的值为9。

  2、对于直接输出表达式的值的计算,如: i = 5; printf("%d",(++i)+(i++)+(++i)+(++i));

  (1) BC下:

  总体原则:从左到右一项一项的计算,后置++或--在中间计算过程中计算。

  a. 计算++i,得值为6,i的值为6;

  b. 计算i++,得值为6,i的值为7;

  c. 计算++i,得值为8,i的值为8;

  d. 计算++i,得值为9,i的值为9;

  所以表达式的值为:6 + 6 + 8 + 9 = 29。

  (2) VC下:

  计算方法同赋值计算,表达式的值为:27。
另附vc++6.0:
int x=3,y=5,z;
//z = x*(y++)+y;// z = 20
//z = x+(++x)*y;// z = 24
//z = x+3+(++x)*y;// z = 27
//z = x+x+(++x)*y;// z = 26
//z = (++x)+x*y;// z = 24
//z = x*(y++)+(++x)*y;// z = 30
//z = (++x)+(++x)+(++x);// z = 16
//z = (x++)+(x++)+(x++);// z = 9
总结:有副作用的表达式与编译器相关,应尽量少用。如面笔试题出现此类问题,可大胆填上:不是我不会,是题没答案;
参考:

细说C/C++中的表达式运算顺序与求值顺序

http://woxiangblog.appspot.com/log-32.html

C语言运算符优先级和结合性与表达式求值顺序

http://hi.chinaunix.net/?uid-20792635-action-viewspace-itemid-32448

csdn论坛:http://topic.csdn.net/u/20090516/10/f27bd123-d68f-4406-bec5-60d83ce49ffa.html