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++依然是高效的語言。當然這些問題只需要注意就行了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章