#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黨,就不測試了,感興趣的童鞋可以調整編譯選項自己試試看