函數在實現過程內存中的壓棧和出棧



關於函數在調用過程中的壓棧和出棧問題在學習的時候就感覺很經典,對程序的把握可以提升一個臺階。
一.首先讓我們寫出一個簡單的函數。(我是在vc6.0中實現,並不表示vs編譯器底下不可以實現)。

#include<stdio.h>

int add(num1,num2)
{
int ret = 0;
ret = num1+num2;
return ret;
}

int main()
{
int num1 = 1;
int num2 = 2;
int ret = add(num1,num2);
printf("%d ",ret);
return 0;
}
1).需要聲明是add函數中可以直接寫成"return num1+num2",我在寫博客的時候是故意寫成這樣,以便於後面的分析。
二.接下來,我們首先明確幾個知識點。
1).棧
 首先必須明確一點也是非常重要的一點,棧是向下生長的,所謂向下生長是指從內存高地址->低地址的路徑延伸,那麼就很明顯了,棧有棧底和棧頂,那麼棧頂的地址要比棧底低。對x86體系的CPU而言,其中

—> 寄存器ebp(base pointer )可稱爲“幀指針”或“基址指針”,其實語意是相同的。
—> 寄存器esp(stack pointer)可稱爲“ 棧指針”。
要知道的是:
—>ebp 在未受改變之前始終指向棧幀的開始,也就是棧底,所以ebp的用途是在堆棧中尋址用的。
—>esp是會隨着數據的入棧和出棧移動的,也就是說,esp始終指向棧頂。
2).
假設函數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調用前的狀態。
如下圖所示:

這裏寫圖片描述
三.在明確了這些知識之後,讓我們返回上面那個簡單的函數。
1).首先來看看我畫出的圖:
這裏寫圖片描述

  上面的圖片能夠粗略的表現函數調用的過程。
2)所產生的彙編代碼:

這裏寫圖片描述

這裏寫圖片描述

 上面兩幅圖片是mian函數的棧幀。

這裏寫圖片描述
上面的圖片是add函數的棧幀。
3).在liunx平臺下的彙編代碼

這裏寫圖片描述

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章