有关i=(++i)+(i++)这种东西的深刻解释,不单单是简单粗暴undefined behavior。ide
====函数
一.反作用(side effect)spa
表达式有两种功能:每一个表达式都产生一个值( value ),同时可能包含反作用( side effect )。反作用是指改变了某些变量的值。code
如:blog
1:20 //这个表达式的值是20;它没有反作用,由于它没有改变任何变量的值。ci
2:x=5 // 这个表达式的值是5;它有一个反作用,由于它改变了变量x的值。编译器
3:x=y++ // 这个表达示有两个反作用,由于改变了两个变量的值。同步
4:x=x++ // 这个表达式也有两个反作用,由于变量x的值发生了两次改变。io
二.求值顺序点for循环
表达式求值规则的核心在于 顺序点( sequence point ) [ C99 6.5 Expressions 条款2 ] [ C++03 5 Expressions 概述 条款4 ]。
顺序点的意思是在一系列步骤中的一个“结算”的点,语言要求这一时刻的求值和反作用所有完成,才能进入下面的部分。在C/C++中只有如下几种存在顺序点:
1)分号;
2)未重载的逗号运算符的左操做数赋值以后(即','处)
3)未重载的'||'运算符的左操做数赋值以后(即'||'处);
4)未重载的'&&'运算符的左操做数赋值以后(即"&&"处);
5)三元运算符'? : '的左操做数赋值以后(即'?'处);
6)在函数全部参数赋值以后但在函数第一条语句执行以前;
7)在函数返回值已拷贝给调用者以后但在该函数以外的代码执行以前;
8)每一个基类和成员初始化以后;
9)在每个完整的变量声明处有一个顺序点,例如int i, j;中逗号和分号处分别有一个顺序点;
10)for循环控制条件中的两个分号处各有一个顺序点。
对于任意一个顺序点,它以前的全部反作用都已经完成,它以后的全部反作用都还没有发生。
在两个顺序点之间,子表达式求值和反作用的顺序是不一样步的。若是代码的结果与求值和反作用发生顺序相关,称这样的代码有不肯定的行为(unspecified behavior).并且,假如期间对一个内建类型执行一次以上的写操做,则是未定义行为.
任意两个顺序点之间的反作用的发生顺序都是未定义的.
如:
x=x++;
该表达式只有一个顺序点,在该顺序点以前有2个反作用,一个是自增,一个赋值,这两个反作用发生的顺序是未定义的,即自增运算和赋值运算哪个先执行是没有被定义的(注意这个顺序跟运算符的优先级是无关的,注意理解运算符优先级的含义),这个执行次序交由编译器厂商去自行决定,所以对于不一样的编译器可能会得出不一样的结果。
#include <stdio.h> #include <stdlib.h> int main(int argc, char*argv[]) { int i=0; int m=(++i)+(++i)+(++i)+(++i); printf("%d %d\n",m,i); system("pause"); return0; }
对于上述代码:
在gcc编译器中运行获得的结果是 11 4
而在Visual Studio 2008中运行获得的结果是 16 4
由于对于
int i=0;
int m=(++i)+(++i)+(++i)+(++i);
在两个分号之间有5个反作用,这5个反作用与子表达式的求值顺序是未定义的,对于不一样的编译器会得出不一样的结果。
而且在这期间对i进行了不止一次的写操做,这也是一个未定义的行为,可能会引发任何后果。
还好比:
x[i]=i++;
printf("%d %d\n",i++,i++);
function(x,x++);
这些都是未定义的行为。
所以咱们平时在写代码时,尽可能不要写出这样风格很差的代码,由于它不只会给程序带来不肯定性,可能会引发任何后果(好比程序崩溃),并且对于代码的移植性来讲是致命的打击。
好比:
x[i]=i++;
能够用这段代码去代替:
x[i]=i;
i++;
function(x,x++);-> function(x,x);x=x+1;
这样的代码才是风格良好的代码。
尽可能保证,在两个相邻顺序点之间同一个变量不能够被修改两次以上或者同时有读取和修改,不然,就会产生未定义的行为。