int i = 1;
int a = i++ + i++;
这是一个很多初学都喜欢问题,是某些人喜欢考的问题,是某些人喜欢自以为是地回答的问题。
见上一篇文章,所谓的结合性和优先级是怎么回事,而结合性和优先级在这里没有决定性的影响。
一般的命令式的语言,都是有一些可操作的对象,通过一些操作,改变这些对象的状态,最后
达到计算的目的。换句话说,是靠副作用进行的计算。里面一般会有两种语法结构:
表达式,语句。
而表达式后面加上;会成为表达式语句。
表达式会有值,会有类型,求值过程就是计算的过程。
语句的话,纯粹是进行某个操作,没有值,没有类型。(一些实现可以对这个进行扩展)
换句话说,语句就是为了产生副作用。
而表达式可能有副作用!!!
比如:
a = i++;
有两个副作用,一个是改变a的值,另一个是改变i的值。
如果忽略掉对a的值的改变,那么副作用只有一个了。
上面讲的是副作用,现在看看求值顺序:
int a = b + c;
+
b c
在计算右边的表达式的值的时候,首先要知道b和c的值,但是b和c究竟哪个先知道,这是没有指定的。
但是b,c在+之前是可以肯定的,这是一个天然的依赖关系(2003标准中似乎是没有提,n3225中有提)。
求值顺序和副作用产生的顺序有什么关系?这里不提,规避之。
n3225中如下说到:
在一个数量对象上的副作用,如果和另一个在相同对象上的副作用,或者使用了这个对象的值的计算,
之间是没有指定顺序的话,那么行为是未定义的。
看几个例子(引自n3225):
void f(int, int);
void g(int i, int* v) {
i = v[i++]; // the behavior is undefined
[=的两个子表达式中,右边的++对i有副作用,=对i的赋值有副作用,虽然在求值上要先分别对i和v[i++]求值
但是对副作用而言,是不清楚哪个在哪个前的,如果++在前,结果就是v[i],否则是=在前,结果就是v[i]+1]。
i = 7, i++, i++; // i becomes 9
[行为确定,左边的副作用在右边之前产生]
i = i++ + 1; // the behavior is undefined
[两个副作用的顺序的问题]
i = i + 1; // the value of i is incremented
[只有一个副作用]
f(i = -1, i = -1); // the behavior is undefined
[看上去,从结果上讲,这个式子的结果是完全可以定义的,但是满足了未定义行为的定义,所以是未定义行为]
}
PS:
只是求值的顺序未确定也会有一些东东:
int f1(){printf("hello ");return 0;}
int f2(){printf("world! ");return 0;}
int a = f1()+f2();
先f1还是先f2是没有指定的。
如果认为这种输出是一个副作用,那么这里求值的未指定导致了副作用顺序的未指定。
int gx = 5;
int f1(){return gx++;}
int f2(){return gx++;}
double y = 1.0*f1()/f2();
只看对y的定义,只知道f1(),f2()求值顺序未指定,再看里面,可以知道求值顺序的未指定导致了副作用的未指定。
注1:在ISO/IEC 14882(2003)中的陈述中指出这样的行为未定义,但是在例子中使用的是未指定行为。
在n3225中,在陈述中使用的是未定义行为,在例子中也用的是未定义行为。
注2:在n3225已经丢弃了“序列点”的概念了,在2003版的标准中也有很多不清楚的,写得很纠结。
2011/01/19 23:30
创建
2013/06/29
读者可以参考haskell中的monad