【補充聲明】此文完成于幾年前回答 BCCN 論壇的網友提問,就問題本身而言,對于這個問題似乎是沒必要深究的,因為這種代碼在讀取一個變量的值的過程中反複嘗試修改它的值,其結果依賴編輯器的實作。這種代碼當然也是不可能在現實應用中出現的。不過作為一個問題,如果他一定要問,某編譯器為什麼會給出這樣的結果,那就必須了解編譯器對這個代碼的編譯結果細節,這就是本文所論述的東西。本文隻涉及到了 TC2, VC6, VC2005 幾種編譯器。而且後兩者應該使用的 WIN32 DEBUG,通常 Release 版本和 Debug 版本是一種在運作結果表現上的等效關系,對這個具體問題在當時我并未有精力再去細分。此次更新編輯順便修改了原文中的個别錯别字。原文中的術語“堆棧”修改為“棧”。原文中的一些中間結論不夠準确,但暫未删除,已經用删除線做了處理。
---- hoodlum1980 ,2012年9月10日。
首先我們來看一下這個問題的提出,來自于一個網友的提問:
<a href="http://bbs.bccn.net/thread-200774-1-1.html">http://bbs.bccn.net/thread-200774-1-1.html</a>
----------------------------------------------------------------------------------------------------------
很簡單的程式
void main()
{
int i=8;
printf("%d,%d,%d,%d\n", ++i, --i, i++, i--);
}
但是結果為(8 7 8 8)無論是從左到右順序求值還是從右到左順序求值都不應該是這個結果吧?
我覺得從左到右應該是(9 8 8 9 )從右到左是(8 7 7 8),
是我的錯還是編譯器的原因?如果是從右到左順序求值,為什麼結果不是(8 7 7 8)而是(8 7 8 8)
請大家指點一下!
[ 本帖最後由 默默無紋 于 2008-2-24 21:04 編輯 ]
-----------------------------------------------------------------------------------------------------------
在這裡我使用了VS.NET2005編譯的結果是:8,8,7,8。用TC2.0編譯的結果是:8,7,7,8。VC6.0我沒有安裝,是以沒有試過,也沒辦法分析。
這裡我們可以看到,由不同的編譯器産生了不同結果,可見這個問題是依賴編譯器的了解和實作的。換句話說,對于 i++ 和 ++i 的處理在這裡是有歧義的,當然在自己應用中我相信也不會有任何人寫出這樣的代碼。但是作為一個問題,我們有必要分析一下不同編譯器究竟如何了解i++和++i操作符的。
我們在學習C的時候,應該已經大概知道了 i++ 和 ++i 兩者的差別,即“++”符号在 i 之前還是之後,決定了 i 自增操作和他的語句的執行順序的關系。即i++,了解為i在其語句中取原始值,++i在其語句中取自增後的新值。這一點是毫無疑義的。但是問題在于,網友的問題中又涉及到了 i++,++i 在作為參數時候的處理,是以這時候我們就會感到困惑,i++ 和 ++i 在作為參數的時候,和進入棧的順序之間有何關系呢?根據前面的實驗,可見TC2.0和VS.net2005的處理不同,可見兩者對其處理不同,那麼造成這種不同的結果的原因是什麼呢?我們從代碼上無法看到差異,是以我們必須看彙編語言才能知曉,編譯器到底把我們的代碼翻譯成了什麼樣子。下面我采用IDA反彙編編譯器把生成的.exe檔案,結果如下:

VS.NET 2005的代碼
mov [ebp+var_8], 8 //i=8
.text:004113E5 mov eax, [ebp+var_8] //
.text:004113E8 mov [ebp+var_D0], eax //i--之前把i的值儲存到,temp[0]=8
.text:004113EE mov ecx, [ebp+var_8]
.text:004113F1 sub ecx, 1 //i--,(從右向左數第一個參數)
.text:004113F4 mov [ebp+var_8], ecx //i=7
.text:004113F7 mov edx, [ebp+var_8]
.text:004113FA mov [ebp+var_D4], edx //i++之前把i儲存到:temp[1]=7 (這時候已經執行過i--了)
.text:00411400 mov eax, [ebp+var_8] //i++,(從右向左數第二個參數)
.text:00411403 add eax, 1
.text:00411406 mov [ebp+var_8], eax //i=8
.text:00411409 mov ecx, [ebp+var_8]
.text:0041140C sub ecx, 1 //--i, 無需儲存修改前的值,直接改變實參i
.text:0041140F mov [ebp+var_8], ecx //i=7
.text:00411412 mov edx, [ebp+var_8]
.text:00411415 add edx, 1 //++i,
.text:00411418 mov [ebp+var_8], edx //i=8
.text:0041141B mov esi, esp
.text:0041141D mov eax, [ebp+var_D0] //壓棧temp[0]=8
.text:00411423 push eax
.text:00411424 mov ecx, [ebp+var_D4] //壓棧temp[1]=7
.text:0041142A push ecx
.text:0041142B mov edx, [ebp+var_8] //壓棧i=8
.text:0041142E push edx
.text:0041142F mov eax, [ebp+var_8] //壓棧i=8
.text:00411432 push eax
.text:00411433 push offset aDDDD ; "%d,%d,%d,%d\n" //壓棧字元串"%d,%d,%d,%d\n"的位址
.text:00411438 call ds:printf //調用列印函數,輸出8,8,7,8
.text:0041143E add esp, 14h
.text:00411441 cmp esi, esp
.text:00411443 call sub_41113B
.text:00411448 mov esi, esp
可見,++i和--i執行的時候直接改變了i的值,而i++和i--必須在所在的這個語句執行後才能改變i的值,是以i++作為參數時,實際上是這樣的過程,
printf("%d",i++);
相當于下面的語句:
int temp=i;
i = (i+1);
printf("%d",temp);
是以上面的代碼可以翻譯為:
int i=8;
printf("%d,%d,%d,%d",++i,--i,i++,i--);
是以可以翻譯為下面的等效代碼:
i=8;
temp0=i; //temp0=8;
i--; //7
temp1=i; //temp1=7
i++; //8
--i; //7
++i; //i=8
printf("%d,%d,%d,%d",i,i,temp1,temp0);
是以列印結果是8,8,7,8
---------------------------------------------------------------------------------------------------------
我們再看在TC2.0下的反彙編代碼:

TC2.0下面的反彙編代碼
----------------------TC2.0反彙編結果--------------------
0:01FA sub_1FA proc near ; CODE XREF: start+11A