本文共 2503 字,大约阅读时间需要 8 分钟。
所有抽象数据类型(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#include 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#include 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.