C语言函数调用堆栈过程

函数调用堆栈过程

调用约定

函数的调用约定很多,常见的包括__stdcall,__cdecl,__fastcall,__thiscall等等。
主要的区别在于约束的三个事件,一个是参数传递是从左开始呢还是从右开始,还有就是堆栈清理的清理方是调用者还是被调用者。另外来说不同的函数调用约定函数产生的符号名称不同

举个栗子,对于cdecl,参数是从右到左传递,堆栈平衡是由调用函数来执行的;而win32API一般使用的是stdcall,参数同样是采用了从右往左传递,而函数的堆栈平衡则是由被调用函数执行(不支持可变参数);fastcall参数直接放入寄存器而非栈中,规定前两个参数分别放入ecx和edx中,当寄存器用完时候参数才按照从右往左的顺序压入堆栈。

调用约定使用场景
_cdeclc调用约定
_stdcallwindows标准调用约定
_fastcall快速调用约定
_thiscallC++成员函数调用约定

压栈过程

int add(int a, int b)
{
    return a+b;
}

int main()
{
    int a = 1;
    int b = 2;
    int res = add(a,b);
    return 0;
}
  1. 首先从main函数初始,ebp和esp分别存放函数的栈底地址和栈顶地址,此时ebp-4即是a,ebp-8则是b的地址。
  2. 然后调用函数add,第一先将参数从右往左依次入栈,push在调用方的函数栈当中,也就是说此时esp往里开辟了两个参数
    图片描述
  3. 执行call指令,首先将下一条指令地址进行入栈,
    图片描述
  4. 随后开辟新栈,进行现场保护
    图片描述

    • 这里省略了现场保护的过程,主要做的就是push了多个寄存器(例如edi,ebx,edi)的值,在完成后还原现场进行pop,对程序没有什么其他影响这里省略。
    • 将edi置为栈顶地址,ecx置为11h,eax置为0CCCCCCCCh。
    • 从edi开始循环拷贝eax,也就是将整个栈内初始化为0CCCCCCCCh,也就是常见的“烫”。
  5. 执行add函数
    开辟一个临时变量,值是(a)(ebp+8) + (b)(ebp+0Ch),将这个值放入eax中。
  6. 执行完成后回退栈针
    图片描述

    • 首先mov esp,ebp 将add的函数栈回退。
    • 随后pop,将[ebp]的值弹出给ebp,也就是ebp弹回退到main函数的栈底。
    • 执行ret指令,将下一条指令的地址弹出给eip寄存器。
    • 随后在main函数的函数栈中回退形参变量所占用的内存 add esp+8。

那么再来看看其他情况

上面的返回值是一个int类型,也就是C的内置类型,通过eax寄存器带出。

如果是一个double或者long long呢?那么可以通过eax、edx两个寄存器带出。

如果是一个自定义类型呢?其实也是类似的:

  • 首先在参数传参过程中不能直接去push一个寄存器了,而现在是通过开辟内存后,将自定义类型的实参b的地址放入esi中,循环赋给实参。
    例如说自定义类型的b参数
    图片描述
  • 参数传递完成之后,再来看看返回值,返回值首先会在压入所有的形参之后,将main函数中返回值(临时量)的地址压入参数。
    图片描述
  • 对返回值的操作也是类似的,通过esi和edi、ecx,循环拷贝到main函数的函数栈之中。
  • 临时量返回值的地址是最后才会压栈的,那么它的地址一定是ebx+8
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值