C 語(yǔ)言中,術(shù)語(yǔ)副作用(side effect)是指對數據對象或者文件的修改。例如,以下語(yǔ)句 var = 99;
的副作用是把 var 的值修改成 99。對表達式求值也可能產(chǎn)生副作用,例如:
se =100
對這個(gè)表達式求值所產(chǎn)生的副作用就是 se 的值被修改成 100。
序列點(diǎn)(sequence point)是指程序運行中的一個(gè)特殊的時(shí)間點(diǎn),在該點(diǎn)之前的所有副作用已經(jīng)結束,并且后續的副作用還沒(méi)發(fā)生。
C 語(yǔ)句結束標志——分號(;)是序列點(diǎn)。也就是說(shuō),C 語(yǔ)句中由賦值、自增或者自減等引起的副作用在分號之前必須結束。我們以后會(huì )說(shuō)到一些包含序列點(diǎn)的運算符。任何完整表達式(full expression)運算結束的那個(gè)時(shí)間點(diǎn)也是序列點(diǎn)。所謂完整表達式,就是說(shuō)這個(gè)表達式不是子表達式。而所謂的子表達式,則是指表達式中的表達式。例如:
f = ++e% 3
這整個(gè)表達式就是一個(gè)完整表達式。這個(gè)表達式中的 ++e、3 和 ++e % 3 都是它的子表達式。
有了序列點(diǎn)的概念,我們下面來(lái)分析一下一個(gè)很常見(jiàn)的錯誤:
int x =1, y;
y = x+++ x++;
這里 y = x++ +x++ 是完整表達式,而 x++ 是它的子表達式。這個(gè)完整表達式運算結束的那一點(diǎn)是一個(gè)序列點(diǎn),int x = 1, y; 中的 ; 也是一個(gè)序列點(diǎn)。也就是說(shuō),x++ + x++ 位于兩個(gè)序列點(diǎn)之間。標準規定,在兩個(gè)序列點(diǎn)之間,一個(gè)對象所保存的值最多只能被修改一次。但是我們清楚可以看到,上面這個(gè)例子中,x 的值在兩個(gè)序列點(diǎn)之間被修改了兩次。這顯然是錯誤的!這段代碼在不同的編譯器上編譯可能會(huì )導致 y 的值有所不同。比較常見(jiàn)的結果是 y 的值最后被修改為 2 或者 3。在此,我不打算就這個(gè)問(wèn)題作更深入的分析,各位只要記住這是錯誤的,別這么用就可以了。有興趣的話(huà),可以看看以下列出的相關(guān)資料。
C 語(yǔ)言標準對副作用和序列點(diǎn)的定義如下:
Accessing a volatile object,modifying an object, modifying a file, or calling a function that does any ofthose operations are all side effects, which are changes in the state of theexecution environment. Evaluation of an expression may produce side effects. Atcertain specified points in the execution sequence called sequence points, allside effects of previous evaluations shall be complete and no side effects ofsubsequent evaluations shall have taken place.
翻譯如下:
訪(fǎng)問(wèn)易變對象,修改對象或文件,或者調用包含這些操作的函數都是副作用,它們都會(huì )改變執行環(huán)境的狀態(tài)。計算表達式也會(huì )引起副作用。執行序列中某些特定的點(diǎn)被稱(chēng)為序列點(diǎn)。在序列點(diǎn)上,該點(diǎn)之前所有運算的副作用都應該結束,并且后繼運算的副作用還沒(méi)發(fā)生。
----------------------------------------------------------------------------------------------
讓我們來(lái)看看下面的代碼:
int i=7; printf(”%d\n”, i++ * i++);
你認為會(huì )返回什么?56?no。正確答案是返回 49?很多人會(huì )問(wèn)為什么?難道不該打印出56嗎?在ccfaq中有非常詳盡的解釋?zhuān)驹蛟谟?/span>c中的序列點(diǎn)。
請注意,盡管后綴自加和后綴自減操作符 ++ 和 — 在輸出其舊值之后才會(huì )執行運算,但這里的“之后”常常被誤解。沒(méi)有任何保證確保自增或自減會(huì )在輸出變量原值之后和對表達式的其它部分進(jìn)行計算之前立即進(jìn)行。也不能保證變量的更新會(huì )在表達式 “完成” (按照 ANSI C 的術(shù)語(yǔ), 在下一個(gè)”序列點(diǎn)”之前) 之前的某個(gè)時(shí)刻進(jìn)行。本例中, 編譯器選擇使用變量的舊值相乘以后再對二者進(jìn)行自增運算。只有到達一個(gè)序列點(diǎn)之后,自增運算才能保證真正被執行。
包含多個(gè)不確定的副作用的代碼的行為總是被認為未定義。(簡(jiǎn)單而言, “多個(gè)不確定副作用”是指在同一個(gè)表達式中使用導致同一對象修改兩次或修改以后又被引用的自增,自減和賦值操作符的任何組合。這是一個(gè)粗略的定義。) 甚至都不要試圖探究這些東西在你的編譯器中是如何實(shí)現的 (這與許多 C 教科書(shū)上的弱智練習正好相反);正如 K&R 明智地指出,”如果你不知道它們在不同的機器上如何實(shí)現, 這樣的無(wú)知可能恰恰會(huì )有助于保護你”。
那么,所謂的序列點(diǎn)是什么意思呢?
序列點(diǎn)是一個(gè)時(shí)間點(diǎn)(在整個(gè)表達式全部計算完畢之后或在 ||、 &&、 ? : 或逗號 運算符處, 或在函數調用之前), 此刻塵埃落定,所有的副作用都已確保結束。 ANSI/ISO C 標準這樣描述:
在上一個(gè)和下一個(gè)序列點(diǎn)之間,一個(gè)對象所保存的值至多只能被表達式的計算修改一次。而且前一個(gè)值只能用于決定將要保存的值。
第二句話(huà)比較費解。它說(shuō)在一個(gè)表達式中如果某個(gè)對象需要寫(xiě)入, 則在同一表達式中對該對象的訪(fǎng)問(wèn)應該只局限于直接用于計算將要寫(xiě)入的值。這條規則有效地限制了只有能確保在修改之前才訪(fǎng)問(wèn)變量的表達式為合法。
例如 i = i+1 合法,而 a[i] = i++ 則非法。為什么這樣的代碼:a[i] = i++; 不能工作?子表達式 i++ 有一個(gè)副作用 — 它會(huì )改變 i 的值 — 由于 i 在同一表達式的其它地方被引用,這會(huì )導致無(wú)定義的結果,無(wú)從判斷該引用(左邊的 a[i] 中)是舊值還是新值。那么,對于 a[i] = i++; 我們不知道 a[] 的哪一個(gè)分量會(huì )被改寫(xiě),但 i 的確會(huì )增加 1,對嗎?
不一定!如果一個(gè)表達式和程序變得未定義,則它的所有方面都會(huì )變成未定義。
為什么&& 和 || 運算符可以產(chǎn)生序列點(diǎn)呢?這些運算符在此處有一個(gè)特殊的例外:如果左邊的子表達式?jīng)Q定最終結果 (即,真對于 || 和假對于 && ) ,則右邊的子表達式不會(huì )計算。因此,從左至右的計算可以確保,對逗號表達式也是如此。而且,所有這些運算符 (包括 ? : ) 都會(huì )引入一個(gè)額外的內部序列點(diǎn)。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
原文來(lái)源:http://kimva.blogbus.com/logs/19148550.html
聯(lián)系客服