原文地址:http://www.titilima.cn/show-234-1.html
結論:
vs2008 一、先計算完所有的++i 將i的結果放入i中 二、電腦運算式的值 三、再計算所有的i++
G++也是先忽略i++,不過和vs2008區別之處在於不是先計算完所有的++i,再電腦運算式,而只是對每一個運算子先計算++i
好了,就從最為臭名昭著的“(++i) + (++i) + (++i)”開始吧。
C++代碼
int i = 4;
int a = (++i) + (++i) + (++i);
題目要求是求a的值,多見於各種等級考試、期末考試的選擇題。
顯然,這道題的考點是首碼自增運算子。與之相似的還有尾碼自增(減)或前尾碼增減混合的情況。
一牆之隔,圍城內外。在象牙塔外的世界,這個題目是最早遭到詬病者之一。因為,出題者所預設的程式運行環境是Turbo C,所以標準答案自然也就是TC的運行結果(有TC的朋友們不妨試一試,看看TC的結果是不是你那捲子上的標準答案)。而事實上,對於這個題目的結果,a 的值是無法預期的——C/C++標準規定,三個++i的子運算式是沒有求值順序點的,同時它們又是有副作用的,因此語言本身並不能保證副作用的順序。
眼見為實,讓我們來看看三款不同編譯器產生的代碼吧。我為每個必要的細節加了注釋,以便理解。
彙編代碼
; Visual C++ 6.0 sp6:
mov [ebp+i], 4 ; i = 4
mov eax, [ebp+i]
add eax, 1 ; ++i, i == 5
mov [ebp+i], eax
mov ecx, [ebp+i]
add ecx, 1 ; ++i, i == 6
mov [ebp+i], ecx
mov edx, [ebp+i]
add edx, [ebp+i] ; 6 + 6 == 12
mov eax, [ebp+i]
add eax, 1 ; ++i, i == 7
mov [ebp+i], eax
add edx, [ebp+i] ; 12 + 7 == 19
mov [ebp+a], edx ; a = 19
; Visual Studio 2005:
mov [ebp+i], 4 ; i = 4
mov eax, [ebp+i]
add eax, 1 ; ++i, i == 5
mov [ebp+i], eax
mov ecx, [ebp+i]
add ecx, 1 ; ++i, i == 6
mov [ebp+i], ecx
mov edx, [ebp+i]
add edx, 1 ; ++i, i == 7
mov [ebp+i], edx
mov eax, [ebp+i]
add eax, [ebp+i] ; 7 + 7 == 14
add eax, [ebp+i] ; 14 + 7 == 21
mov [ebp+a], eax ; a = 21
; gcc.exe (GCC) 3.4.5 (mingw-vista special):
mov [ebp+i], 4 ; i = 4
lea eax, [ebp+i]
inc dword ptr [eax] ; ++i, i == 5
lea eax, [ebp+i]
inc dword ptr [eax] ; ++i, i == 6
mov eax, [ebp+i]
mov edx, [ebp+i]
add edx, eax ; 6 + 6 == 12
lea eax, [ebp+i]
inc dword ptr [eax] ; ++i, i == 7
mov eax, edx
add eax, [ebp+i] ; 12 + 7 == 19
mov [ebp+a], eax ; a = 19
——其實我大可不必列出如是這般冗長的彙編代碼,而只需要一個a值結果的總結表格就可以說明問題。不過我還是選擇了組合語言,原因有二:第一,任何的磚家、叫獸告訴你的東西都遠遠不及最終產生的目標代碼可靠;第二,使用彙編代碼可以把自己偽裝成高手,用來裝B的效果肯定比簡單的表格來得有效,何樂而不為哉。
裝都裝了,自然不怕遭雷劈。再來一個嵌入式裝置上的代碼,環境是eMbedded Visual C++ 4.0 sp4的ARMV4編譯器:
彙編代碼
MOV R0, #4 ; i = 4
STR R0, [SP,#8+i]
LDR R1, [SP,#8+i]
ADD R0, R1, #1 ; ++i, i == 5
STR R0, [SP,#8+i]
LDR R1, [SP,#8+i]
ADD R0, R1, #1 ; ++i, i == 6
STR R0, [SP,#8+i]
LDR R1, [SP,#8+i]
ADD R0, R1, #1 ; ++i, i == 7
STR R0, [SP,#8+i]
LDR R1, [SP,#8+i]
LDR R0, [SP,#8+i]
ADD R2, R1, R0 ; 7 + 7 == 14
LDR R3, [SP,#8+i]
ADD R0, R2, R3 ; 14 + 7 == 21
STR R0, [SP,#8+a] ; a = 21
相信到這裡諸位都看到了,一個運算式在不同的編譯器上會出現不同的結果——特別是微軟的VC6和VS2005,一家產的編譯器的結果也是不一樣的。亦即是說,倘使你寫下了諸如“(++i) + (++i) + (++i)”這樣的代碼,你得到的結果將是一個無法預期的結果,必須的。
末了,說點八卦的。很多程式員將這種病態、晦澀的編碼方式歸咎於譚浩強版的《C程式設計》,認為譚老爺子是這種學究代碼的始作俑者。李馬饒有興緻地考證了一番,發現譚老爺子在《C程式設計》(第二版)的第58~59頁中對這種情況進行了討論,並指出以下幾點:
應該避免++/--的副作用可能產生的歧義性,建議將這樣的運算式拆開寫。
對於i+++j的情況,應使用括弧來使代碼明晰以避免誤解,如(i++)+j或i+(++j)。
“總之,不要寫出別人看不懂的、也不知道系統會怎樣執行的程式。”
竊為譚老爺子鳴不平啊。