i++ : 順序點(sequence point) 與 副作用 (side effect)

i++ : 順序點(sequence point) 與 副作用 (side effect)

表達式的計算分爲兩種,一種是有副作用的計算,如:
(++x)+y
一種是無副作用的計算,如:
x*y

有副作用的計算中,子表達式的計算順序是重要的。例如
(++x)*(x+1)
當x=0時,如果先算++x,上式計算結果爲2,如果先算x+1,上式計算結果爲1。
再如,對函數g(int, int)的調用g(x, ++x), 當x=1,這個調用是g(1, 2)還是g(2, 2)?

所謂“順序點”,和表達式的副作用緊密相關。再看這個例子:

(++i) + (++j)


如:int a=3,b;  b=(++a)+(++a) ;  b的結果爲10; 要滿足一次更改的要求,++a優先


這個表達式的計算,有兩個副作用:
i自增1;
j自增1;
但是到底哪一個先發生?答案是:任何答案都不對。

爲什麼?因爲標準並不定義副作用發生的順序。標準只保證,一個表達式的全部副作用,不在達到該表達式緊鄰的前一順序點前發生,並且一定在達到該表達式緊鄰的下一個順序點之前發生完畢。

一個順序點,被定義爲程序執行過程中的這樣一個點:該點前的表達式的所有副作用,在程序執行到達該點之前發生完畢;該點後的表達式的所有副作用,在程序執行到該點時尚未發生。

(++i) + (++j)這個表達式本身不包含順序點,所以i++,j++這兩個“副作用”到底誰先發生,根據標準,是未定義的。如果給這個表達式加上順序點,如:
;(++i) + (++j);
標準只保證,這兩個副作用在整個表達式求值完成前(即到達後面的順序點";"前)都會發生,並且不會在上一個語句執行完畢之前發生。

標準還規定,兩個相鄰順序點之間,對某一表達式求值,最多隻能造成任一特定對象的值被更改一次。如果表達式求值過程會更改某對象的值,那麼要求更改前的值被讀取的唯一目的,只能是用來確定要存入的新值。
例如下面的表達式,按照標準規定,執行結果是未定義的:
(i++)+(i++)
這個表達式本身不包含任何順序點,但是對這個表達式求值,按照運算符定義,將更改i兩次,違反了“一次更改”的要求。
再看下面的表達式,按照標準規定,執行結果也是未定義的:
x=i++
這個表達式本身不包含任何順序點,雖然i的值只更改了一次,但是x這個左值中,i被讀取,用於確定數組中被修改的元素的下標。這次對i求值和i++肯定位於同一對順序點之間,該表達式求值過程更改了i的值,x中讀取i卻不是爲了確定i的新值,這違反了“讀取只能用於確定新值”規定。

任何對相鄰順序點間表達式求值的多個副作用發生的順序進行假設,或者違反上述“一次更改、讀取僅用於確定新值”規定的代碼,其執行結果都是未定義的。這裏所說的“未定義”,通常比“不可移植”更嚴重,可以認爲是“錯誤”的同意詞。

通常我們認爲,標準對“順序點”及其語義的定義,是爲了嚴謹地定義C/C++的表達式和求值過程,並不是爲了讓程序員通過對順序點的掌握,(過分地)利用表達式求值的副作用。實際工作中,我們完全可以通過引入中間變量,避開“順序點”這樣容易出錯,也極大地降低代碼可讀性的“邊緣概念”。



關於 “未定義” (undefined),參考:http://en.wikipedia.org/wiki/Undefined_behavior

關於 “順序點”(sequence point),參考:http://en.wikipedia.org/wiki/Sequence_point
發佈了57 篇原創文章 · 獲贊 9 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章