從一個新手容易混淆的例子簡單分析C語言中函數調用過程


        某天,王尼瑪寫了段C程序:

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

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM