1. 程式人生 > >棧的分析(一)————函式呼叫棧

棧的分析(一)————函式呼叫棧

當發生函式呼叫的時候,棧空間中存放的資料是這樣的:
1、呼叫者函式把被調函式所需要的引數按照與被調函式的形參順序相反的順序壓入棧中,即:從右向左依次把被調函式所需要的引數壓入棧;
2、呼叫者函式使用call指令呼叫被調函式,並把call指令的下一條指令的地址當成返回地址壓入棧中(這個壓棧操作隱含在call指令中);
3、在被調函式中,被調函式會先儲存呼叫者函式的棧底地址(push ebp)(從高內在地址–》低記憶體地址),然後再儲存呼叫者函式的棧頂地址,即:當前被調函式的棧底地址(mov ebp,esp);
4、在被調函式中,從ebp的位置處開始存放被調函式中的區域性變數和臨時變數,並且這些變數的地址按照定義時的順序依次減小,即:這些變數的地址是按照棧的延伸方向排列的,先定義的變數先入棧,後定義的變數後入棧;
所以,發生函式呼叫時,入棧的順序為:
引數N
引數N-1
引數N-2
…..
引數3
引數2
引數1
函式返回地址
上一層呼叫函式的EBP/BP
區域性變數1
區域性變數2
….
區域性變數N
函式呼叫棧如下圖所示:
這裡寫圖片描述

解釋:
首先,將呼叫者函式的EBP入棧(push ebp),
然後將呼叫者函式的棧頂指標ESP賦值給被調函式的EBP(作為被調函式的棧底,mov ebp,esp),
此時,EBP暫存器處於一個非常重要的位置,該暫存器中存放著一個地址(原EBP入棧後的棧頂),

以該地址為基準,向上(棧底方向)能獲取返回地址、引數值,向下(棧頂方向)能獲取函式的區域性變數值,而該地址處又存放著上一層函式呼叫時的EBP值;

一般而言,SS:[ebp+4]處為被調函式的返回地址,
SS:[EBP+8]處為傳遞給被調函式的第一個引數(最後一個入棧的引數,此處假設其佔用4位元組記憶體)的值,
SS:[EBP-4]處為被調函式中的第一個區域性變數,
SS:[EBP]處為上一層EBP值;由於EBP中的地址處總是”上一層函式呼叫時的EBP值”,

而在每一層函式呼叫中,都能通過當時的EBP值”向上(棧底方向)能獲取返回地址、引數值,向下(棧頂方向)能獲取被調函式的區域性變數值”;

如此遞迴,就形成了函式呼叫棧;
函式內區域性變數佈局示例:

#include <stdio.h>
#include <string.h>
struct C
{
  int a;
  int b;
  int c;
};
int test2(int x, int y, int z)
{
  printf("hello,test2\n");
  return 0;
}
int test(int x, int y, int z)
{
  int
a = 1; int b = 2; int c = 3; struct C st; printf("addr x = %u\n",(unsigned int)(&x)); printf("addr y = %u\n",(unsigned int)(&y)); printf("addr z = %u\n",(unsigned int)(&z)); printf("addr a = %u\n",(unsigned int)(&a)); printf("addr b = %u\n",(unsigned int)(&b)); printf("addr c = %u\n",(unsigned int)(&c)); printf("addr st = %u\n",(unsigned int)(&st)); printf("addr st.a = %u\n",(unsigned int)(&st.a)); printf("addr st.b = %u\n",(unsigned int)(&st.b)); printf("addr st.c = %u\n",(unsigned int)(&st.c)); return 0; } int main(int argc, char** argv) { int x = 1; int y = 2; int z = 3; test(x,y,z); printf("x = %d; y = %d; z = %d;\n", x,y,z); memset(&y, 0, 8); printf("x = %d; y = %d; z = %d;\n", x,y,z); return 0; } 列印輸出如下: addr x = 4288282272 addr y = 4288282276 addr z = 4288282280 addr a = 4288282260 addr b = 4288282256 addr c = 4288282252 addr st = 4288282240 addr st.a = 4288282240 addr st.b = 4288282244 addr st.c = 4288282248 a = 1; b = 2; c = 3; a = 0; b = 0; c = 3;

示例效果圖:

這裡寫圖片描述

該圖中的區域性變數都是在該示例中定義的;

這裡寫圖片描述
這個圖片中反映的是一個典型的函式呼叫棧的記憶體佈局;