函数的调用过程
所有抽象数据类型(ADT)都必须明确一件事——如何获取内存来存储值。内存分配方式有三种:一是从静态存储区域分配(全局变量,static变量);二是在栈上创建(局部变量,自动变量);三是从堆上分配(动态内存分配,用malloc或者new申请多大内存,用free或者delete释放内存)。下面来看一个图理解c程序的内存分配:
从低地址到高地址分别为代码区,文字常量区,已初始化全局数据区,未初始化全局数据区,堆区,栈区。
在执行函数时,函数内的局部变量的储存单元都可以在栈上创建,下面就来详细介绍堆栈(stack)。
一,栈帧的基础知识
1.栈帧的鲜明特点就是先进后出。
2.基本的栈帧操作是push(压入栈顶)和pop(移出栈并返回这个值)。
3.通用寄存器:EAX,EBX,ECX,EDX,esp(栈顶的地址),ebp(栈底的地址);
eip([pc]:程序计数器寄存器,当前正在执行指令的下一条指令的地址);
call:将当前指令的下一条指令进行保存;然后再跳转到目标函数的入口地址(修改eip);
ret:将当前的返回地址出栈;再把弹出的数据修改eip。
二,在vc6.0下实现栈帧(函数调用的过程)
每次函数调用都是一个过程,我们通常称之为函数的调用的程;研究函数调用的过程对应汇编代码。我们接下来通过下面的程序来认识函数调用的过程:
#include<stdio.h>
#include<windows.h>
int myadd(int _a,int _b)
{
int z=_a+_b;
return z;
}
int main()
{
int a=0XAAAAAAAA;
int b=0XBBBBBBBB;
int ret=myadd(a,b);
printf("you should run here! %d\n",ret);
system("pause");
return 0;
}
调用栈的情况:
编写好程序后,按F11进入myadd函数,这时查看堆栈可以看到c程序第一个调用的不是main函数,而是mainCRTStartup()函数;
main函数栈帧结构如下:
接下来进行调试(转成汇编语言):
1. 从main函数的地⽅开始,要展开main函数的调⽤就得为main函数创建栈帧,那我们先来看main函数栈帧的创建:
首先push ebp将ebp(栈底)压栈;mov ebp,esp将esp的值赋给ebp,产生新的ebp;sub esp,4ch给esp减去减去一个16进制的数,产生新的esp.
接下来看mov dword ptr[ebp-4],0AAAAAAAAh 是创建局部变量a(定义并初始化);
mov dword ptr[ebp-8],0BBBBBBBBBh 是创建局部变量b;
下面看下栈分配:
2.接下来是myadd函数的调用:
mov eax,dword ptr[ebp-8]把b放入EAX;push eax把eax的内容压栈;
mov ecx,dword ptr[ebp-4]把a放入EBX;push ebx把ebx的内容压栈;
这时寄存器的内容如下:
然后看call 指令:将当前指令的下一条指令的地址进行保存(这里就是00401093入栈);然后再跳转(jmp)到目标函数的入口地址(修改eip成00401020);
执行call指令,汇编语言跳转到了这里:
执行jmp后又跳转到这里:
这时栈分配为:
3.进入myadd函数执行代码处:
push ebp:将ebp的内容(main函数的栈底)入栈;
move ebp,esp 将esp的内容给ebp(即esb=ebp);
此时栈分配如下:
然后执行sub指令esp-44,ESP下移,此时形成myadd栈帧;
接着执行mov eax,dword ptr[ebp+8]把a放进eax;
add eax,dword ptr[ebp+12]执行a+b;
mov dword ptr[ebp-4],eax的内容给[ebp-4],即z=a+b;
mov eax,dword ptr[ebp-4]把z给eax;
此时栈分配如下:
接着执行到mov esp,ebp把ebp赋给esp;
此时栈分配:
pop ebp 出栈,将出栈的内容保存到ebp,回到main函数的栈帧(栈底回到main,栈顶向上移);
执行ret之后进入:
执行add esp,8:esp=esp+8,栈底加8向上移;
mov dword ptr[ebp-12],eax,将结果储存在eax寄存器里,通过寄存器带回函数的返回值;
此时就完成了栈帧的创建和销毁(函数的调用)。
通过研究函数调用过程我们发现,调用一个函数要形参实例化,会形成临时变量,且形参实例化是从右向左的。那么我们现在如果不访问最右边的参数,通过a修改b的参数,可以实现调用吗?
形参实例化从右向左是因为低地址先入栈,可以定义指针来寻址,通过以下程序可以实现:
#include<stdio.h>
#include<windows.h>
int myadd(int _a,int _b)
{
int z;
int *p;//定义一个指针变量p;
p=&_a;//p指向a;
p++;//指针加一就是加上所指变量的类型,这里指向上一地址;
*p=5;//b=5;
z=_a+_b;
return z;
}
int main()
{
int a=10;
int b=20;
int ret;
ret=myadd(a,b);
printf("you should run here! %d\n",ret);
system("pause");
return 0;
}
这样就实现了通过b来修改a的参数,打印的结果是15.
上一篇: js中判断对象具体类型
下一篇: Struts2的基本配置和使用过程