函数的调用过程



所有抽象数据类型(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.








 

  


             

             

           

                   

             

     

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值