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

    技术2022-05-20  62

     

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

     


    最新回复(0)