C和C++计算的顺序点和副作用

预备知识

副作用和透明引用:

什么是副作用?

专业一点的解释就是一个表达式修改了环境(环境就是其它可修改的数据,如变量),不仅计算值还做了额外的事情。比如表达式:i++,这个表达式就有副作用,因为它修改了环境(i)。还有:

i++ + ++i, j + (i = 3) + k, --i, i-- ...等。

什么是透明引用呢?

即不对环境有影响,比如表达式:(a+b)-(c+d),a、b、c、d在这个表达式中都被透明引用了,因为不管先计算哪一边,这个表达式的结果都不变。

问题:

i = 1;

printf("%d %d", i++, ++i);

这里有一个参数列表的计算顺序,到底是++i,i++还是i++,++i? 有人说参数都是从右住左入栈的,所以是++i,i++,假定是这样的顺序,那么应该输出:2 2

如果是从左住右呢?那么输出:1 3 在gcc 4.4.0下输出: 2 3。是不是感到结果很惊讶,难道它有其它的求值顺序?

这里有一个顺序点的概念,什么是顺序点?比如:

i++ + ... + *p;

假定p指向i。表达式i++是一个有副作用的表达式,我们假定这个表达式的计算顺序是从左往右,那么i++肯定先算,当算到*p的时候,之前i++的副作用是否体现到了内存里面?也就是说是否把新值写到了i对应的内存里面?因为i的值是存放在CPU的寄存器里面的,所以如果编译器没有把新值从CPU写到内存里面,那么*p引用到的将是原来的值。如果已经体现了副作用,那么i++和*p之间就有一个顺序点,也就是体现副作用的地方,在下一个顺序点之前所有的副作用都必须实现,即写入内存。

回到上面i++,++i的问题,如果i++和++i之间有一个顺序点,并且计算顺序也知道,是不是就可以推算出结果呢?确实是这样,比如在Java里就可以推算出结果,因为Java规定表达式的计算顺序从左往右,并且所有副作用都会在下一个计算之前实现。

那么在C\C++里答案是什么呢?答案就是不知道!虽然这样回答有点水,不过确实不知道,如果有人说知道,那么它肯定是专家。

每个编译器都有自己的求值顺序,以及顺序点的规定,但是请放心,一条语句执行完毕之后,这条语句中的所有副作用都会实现,也就是说:
i = 1;
printf("%d %d", i++, ++i);

当printf这行执行之后i的值肯定是3。

在C\C++里,如果一个表达式中要引用一个变量多次,就不应该对该变量做有副作用的运算,只能是透明引用,否则无法推算出结果来,不同的编译器有自己的求值顺序及顺序点的规定,这都是C\C++允许的。C\C++没有规定这些是为了效率考虑,如果像Java那样规定顺序及副作用的体现,必定会有大量的内存访问,所以Java是牺牲了效率而带来了程序的清晰,稳定。但C\C++依然是高效的语言。当然这些问题只需要注意就行了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章