- 相關(guān)推薦
筆試實(shí)例:"序列點(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++ 則非法
拓展:
int i = 3;
i = i++;
cout << i;
結果是什么?有人可能會(huì )說(shuō)是3,也有人可能會(huì )說(shuō)是4,更多的人在罵出題的人白癡,但這語(yǔ)句究竟有何問(wèn)題呢?未必每個(gè)人都清楚。
有些人也許馬上會(huì )說(shuō),這是“未定義行為”。沒(méi)錯,這是一個(gè)典型的未定義行為。i = i++這個(gè)表達式合乎C++語(yǔ)法,能夠順利編譯通過(guò),但是執行的結果,標準說(shuō)“未定義”。為什么是“未定義”,深究起來(lái),要從序列點(diǎn)說(shuō)起。
序列點(diǎn)是程序中這樣的一些點(diǎn):通俗地說(shuō),執行至此,之前的語(yǔ)句都已經(jīng)徹底執行干凈執行完了,之后的語(yǔ)句還完全沒(méi)開(kāi)始執行;更常見(jiàn)、更嚴謹但略晦澀的說(shuō)法是,之前的語(yǔ)句對現場(chǎng)環(huán)境的改變已經(jīng)全部完成,之后的語(yǔ)句對現場(chǎng)環(huán)境的改變還沒(méi)有開(kāi)始。啥是現場(chǎng)環(huán)境呢?就是程序執行到某一點(diǎn)的那個(gè)狀態(tài),包括變量的內容、文件的內容等。
這跟最開(kāi)始那個(gè)例子有什么關(guān)系呢?關(guān)鍵的問(wèn)題來(lái)了:標準規定,兩個(gè)序列點(diǎn)之間,程序執行的順序可以是任意的。沒(méi)錯,正如你猜的那樣,C++標準規定一個(gè)完整的表達式結束之后有一個(gè)序列點(diǎn),而例子中i = i++是位于兩個(gè)序列點(diǎn)之間的。編譯器可以先算完i++,再寫(xiě)結果給i,也可以先將i = i,再令i++。按前面的方法算,i先自增變?yōu)?,然后i++返回3,于是i被賦值為3;按后一種方法算,i先被賦值為3,隨后自增變成4。標準說(shuō)了,這兩種處理方法,編譯器你愛(ài)選那種就選哪種,隨便。如果誰(shuí)寫(xiě)的程序像這樣依賴(lài)執行的順序,讓他自己哭去!
等等,有人要問(wèn)了,++的優(yōu)先級難倒不是高于=嗎?顯然應該先執行++啊。這里有個(gè)概念的問(wèn)題,前一段說(shuō)的編譯器先算i = i,絕不是說(shuō)令=的優(yōu)先級比++還高了。如果那樣的話(huà),表達式將變成 (i = i)++,也就是i.operator = (i). operator ++,執行++的主體變成i = i這個(gè)表達式的返回值了。上一段所說(shuō)的先計算i = i,實(shí)際上還是先計算i++,只不過(guò)是先返回了i的值,然后推遲了將i自增1的操作先去干別的(i = i)去了,回頭再來(lái)給i自增1。
——“什么,你說(shuō)先干別的就先干別的,憑什么!”
嗯,我再重復一遍,標準規定,兩個(gè)序列點(diǎn)之間,程序執行的順序可以是任意的。
——“不是吃飽了撐的嘛,標準搞這個(gè)干啥?嚴格按照順序執行不就完了嘛”。
C++標準弄這么復雜自然是有道理的。C++是極為重視執行效率的語(yǔ)言,這樣做給了編譯器優(yōu)化的空間。比如考慮
int j = i++;
如果非得把i++執行干凈了再干別的,那就不得不 temp = i; i += 1; j = i; 。如果允許編譯器打亂順序執行呢,直接 j = i; i +=1; 就好了,省了一個(gè)temp倒一次的過(guò)程。
多說(shuō)一句,一些更高層的語(yǔ)言,不是像C++這種極為重視效率的,比如Java,上面的例子就完全沒(méi)有問(wèn)題。Java完全不允許你編譯器亂搞,上面那個(gè)例子,在Java中一定是先把i++徹底執行干凈了返回3,再進(jìn)行賦值,賦值完之后不會(huì )再有別的操作了,所以結果一定是3。
如何避免由序列點(diǎn)造成的這種未定義行為,有一句經(jīng)典但有點(diǎn)晦澀的編程規則:“在相鄰的兩個(gè)序列點(diǎn)之間,一個(gè)對象只允許被修改一次,而且如果一個(gè)對象被修改則在這兩個(gè)序列點(diǎn)之間只能為了確定該對象的新值而讀一次”。其實(shí)明白了序列點(diǎn)具體是怎么回事,這個(gè)規則應該就很容易明白了。由于序列點(diǎn)之間程序執行順序不確定,一個(gè)對象被修改多次的話(huà)最后留下的是哪次的結果就不確定。另外如果一個(gè)對象同時(shí)存在讀取和修改,只有根據讀取的結果來(lái)修改才是合法的,否則就會(huì )出現是先改完再讀還是先讀完再改的混亂。
最后再說(shuō)一下最新的C++2003標準中定義的序列點(diǎn)(詳細說(shuō)明請參考標準):
·完整聲明之后
·完整表達式之后
·進(jìn)入函數時(shí)與退出函數時(shí)
·|| && ?: , 四個(gè)操作符的第一個(gè)操作數之后
最后一個(gè)似乎有點(diǎn)奇怪,為啥 + - 操作符之前就沒(méi)有序列點(diǎn),|| &&之前就有呢?a+b之間沒(méi)有序列點(diǎn)而a||b之間就有,不公平啊。
嗯,你猜的沒(méi)錯,是為了短路。
不過(guò)要是手賤重載了默認的||和&&,他們可就視同普通函數,不會(huì )在第一個(gè)操作數之后有序列點(diǎn)了,切記。
【筆試實(shí)例:"序列點(diǎn)" 是什么】相關(guān)文章:
求職筆試常見(jiàn)試題分類(lèi)及實(shí)例解析02-16
求職筆試常見(jiàn)試題分類(lèi)及實(shí)例解析08-11
m序列與gold序列性能分析比較03-07
筆試的特點(diǎn)是什么11-08
南京 宣講會(huì )+筆試 一點(diǎn)點(diǎn)感想08-10
安永香港今年筆試內容是什么?11-21