Wsequence-point

什麼是sequence point?

表達式的計算分為兩種,一種是有副作用的計算,如:
(++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)

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

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

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

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

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

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

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

    立你斯 發表在 痞客邦 留言(0) 人氣()