转自:http://bdxnote.blog.163.com/blog/static/8444235201063083331797/
当发生函数调用的时候,栈空间中存放的数据是这样的: 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; 示例效果图:
该图中的局部变量都是在该示例中定义的;
下图中反映的是一个典型的函数调用栈的内存布局:
|