程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 淺析C++中的序列點

淺析C++中的序列點

編輯:關於C++

int i = 3;

i = i++;

cout << i;

結果是什麼?有人可能會說是3,也有人可能會說是4,更多的人在罵出題的人白癡,但這語句究竟有何問題呢?未必每個人都清楚。

有些人也許馬上會說,這是“未定義行為”。沒錯,這是一個典型的未定義行為。i = i++這個表達式合乎C++語法,能夠順利編譯通過,但是執行的結果,標准說“未定義”。為什麼是“未定義”,深究起來,要從序列點說起。

序列點是程序中這樣的一些點:通俗地說,執行至此,之前的語句都已經徹底執行干淨執行完了,之後的語句還完全沒開始執行;更常見、更嚴謹但略晦澀的說法是,之前的語句對現場環境的改變已經全部完成,之後的語句對現場環境的改變還沒有開始。啥是現場環境呢?就是程序執行到某一點的那個狀態,包括變量的內容、文件的內容等。

這跟最開始那個例子有什麼關系呢?關鍵的問題來了:標准規定,兩個序列點之間,程序執行的順序可以是任意的。沒錯,正如你猜的那樣,C++標准規定一個完整的表達式結束之後有一個序列點,而例子中i = i++是位於兩個序列點之間的。編譯器可以先算完i++,再寫結果給i,也可以先將i = i,再令i++.按前面的方法算,i先自增變為4,然後i++返回3,於是i被賦值為3;按後一種方法算,i先被賦值為3,隨後自增變成4.標准說了,這兩種處理方法,編譯器你愛選那種就選哪種,隨便。如果誰寫的程序像這樣依賴執行的順序,讓他自己哭去!

等等,有人要問了,++的優先級難倒不是高於=嗎?顯然應該先執行++啊。這裡有個概念的問題,前一段說的編譯器先算i = i,絕不是說令=的優先級比++還高了。如果那樣的話,表達式將變成 (i = i)++,也就是i.operator = (i)。 operator ++,執行++的主體變成i = i這個表達式的返回值了。上一段所說的先計算i = i,實際上還是先計算i++,只不過是先返回了i的值,然後推遲了將i自增1的操作先去干別的(i = i)去了,回頭再來給i自增1.

——“什麼,你說先干別的就先干別的,憑什麼!”

嗯,我再重復一遍,標准規定,兩個序列點之間,程序執行的順序可以是任意的。

——“不是吃飽了撐的嘛,標准搞這個干啥?嚴格按照順序執行不就完了嘛”。

C++標准弄這麼復雜自然是有道理的。C++是極為重視執行效率的語言,這樣做給了編譯器優化的空間。比如考慮

int j = i++;

如果非得把i++執行干淨了再干別的,那就不得不 temp = i; i += 1; j = i; .如果允許編譯器打亂順序執行呢,直接 j = i; i +=1; 就好了,省了一個temp倒一次的過程。

多說一句,一些更高層的語言,不是像C++這種極為重視效率的,比如Java,上面的例子就完全沒有問題。Java完全不允許你編譯器亂搞,上面那個例子,在Java中一定是先把i++徹底執行干淨了返回3,再進行賦值,賦值完之後不會再有別的操作了,所以結果一定是3.

如何避免由序列點造成的這種未定義行為,有一句經典但有點晦澀的編程規則:“在相鄰的兩個序列點之間,一個對象只允許被修改一次,而且如果一個對象被修改則在這兩個序列點之間只能為了確定該對象的新值而讀一次”。其實明白了序列點具體是怎麼回事,這個規則應該就很容易明白了。由於序列點之間程序執行順序不確定,一個對象被修改多次的話最後留下的是哪次的結果就不確定。另外如果一個對象同時存在讀取和修改,只有根據讀取的結果來修改才是合法的,否則就會出現是先改完再讀還是先讀完再改的混亂。

最後再說一下最新的C++2003標准中定義的序列點(詳細說明請參考標准):

·完整聲明之後

·完整表達式之後

·進入函數時與退出函數時

·|| && ?: , 四個操作符的第一個操作數之後

最後一個似乎有點奇怪,為啥 + - 操作符之前就沒有序列點,|| &&之前就有呢?a+b之間沒有序列點而a||b之間就有,不公平啊。

嗯,你猜的沒錯,是為了短路。

不過要是手建重載了默認的||和&&,他們可就視同普通函數,不會在第一個操作數之後有序列點了,切記。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved