#include <stdio.h> 2 3 void input() 4 { 5 int i; 6 int array[20]; 7 for(i = 0; i < 20; i++) 8 { 9 array[i] = i; 10 } 11 } 12 13 void output() 14 { 15 int i; 16 int array[20]; 17 for(i = 0; i < 20; i++) 18 { 19 printf("%d\n", array[i]); 20 } 21 } 22 23 int main() 24 { 25 input(); 26 output(); 27 while(1){} 28 return 0; 29 } 復制代碼 這段代碼的目的很簡單,在input函數中定義了array[20]並賦值,在output函數中輸出,運行結果如下: Nice Work! But……在input()後來一發printf()呢????? 復制代碼 1 int main() 2 { 3 input(); 4 printf("any string"); 5 output(); 6 while(1){} 7 return 0; 8 } 復制代碼 其實,只要學過一段時間的C語言的童鞋就會發現,剛剛開始那倆函數裡定義的array[20]就出問題了,這倆array壓根兒沒關系,如果遇到這樣的代碼,第一反應就是通過參數或者全局變量的方法,讓這倆array有關系。 But,問題來了……王尼瑪是個新手,他將兩個array定義成一樣的名字認為他們就是同一個數組,並且,他振振有詞的說,我之前的代碼是沒問題的,只加了個printf就出問題了,應該就是這裡有問題了,怎麼可能是定義array的問題? 尼瑪,這只是巧合而已,你的第一段程序就是錯的! 可我的輸出是正確的啊…… 其實大家都知道,問題的根源是output和input函數中的數組array雖然同名,但卻不是同一個數組,只是碰巧將原先賦值的內存給輸出了而已,要解釋這個問題,就需要了解C語言在函數調用過程中,堆棧是如何變化的。首先必須明確一點也是非常重要的一點,棧是向下生長的,所謂向下生長是指從內存高地址->低地址的路徑延伸,那麼就很明顯了,棧有棧底和棧頂,那麼棧頂的地址要比棧底低。對x86體系的CPU而言,其中 ---> 寄存器ebp(base pointer )可稱為“幀指針”或“基址指針”,其實語意是相同的。 ---> 寄存器esp(stack pointer)可稱為“ 棧指針”。 要知道的是: ---> ebp 在未受改變之前始終指向棧幀的開始,也就是棧底,所以ebp的用途是在堆棧中尋址用的。 ---> esp是會隨著數據的入棧和出棧移動的,也就是說,esp始終指向棧頂。 見下圖,假設函數A調用函數B,我們稱A函數為"調用者",B函數為“被調用者”則函數調用過程可以這麼描述: (1)先將調用者(A)的堆棧的基址(ebp)入棧,以保存之前任務的信息。 (2)然後將調用者(A)的棧頂指針(esp)的值賦給ebp,作為新的基址(即被調用者B的棧底)。 (3)然後在這個基址(被調用者B的棧底)上開辟(一般用sub指令)相應的空間用作被調用者B的棧空間。 (4)函數B返回後,從當前棧幀的ebp即恢復為調用者A的棧頂(esp),使棧頂恢復函數B被調用前的位置;然後調用者A再從恢復後的棧頂可彈出之前的ebp值(可以這麼做是因為這個值在函數調用前一步被壓入堆棧)。這樣,ebp和esp就都恢復了調用函數B前的位置,也就是棧恢復函數B調用前的狀態。 回到之前的問題,由於input函數和output函數為各自的array數組分配的空間在內存中的地址恰好相同,所以可以順利輸出其內容;但是在調用printf函數以後,由於堆棧中一部分內容被修改了,所以輸出結果前半部分是正確的,後半部分是錯誤的。看到這裡,相信有童鞋會試著運行這段代碼,如果使用Turbo C,恭喜你可以獲得相同的結果(上述結果在Turbo C測試截圖);如果使用Visual Studio XXXX,將得到如下結果: 這是怎麼回事呢?查看了反匯編,發現在Debug版本中,為了方便調試,VS會將數組初始化為0xCCCCCCCC,而output函數中的array數組是剛剛定義的,所以被VS初始化位0xCCCCCCCC,轉換成unsigned int就是-858993460。 當然,在Release版本中,為了提高效率,是不會對數組進行這種默認初始化的操作,那麼結果是什麼樣的呢? 納尼?!如果VS不給數組初始化,得到的結果為毛和Turbo C不一樣啊…… 既然這樣,只能再次借助反匯編了,見下圖。可以發現input函數沒有對應的匯編語句,也就是說,由於這貨啥都不干,被編譯器優化掉了。既然沒有對數組array賦值,那麼輸出的自然是內存裡原先亂七八糟的數據了。 至於GCC會得出什麼結果,作為Windows黨,就不測試了,感興趣的童鞋可以調整編譯選項自己試試看