函数的工作原理

2 篇文章 0 订阅
2 篇文章 0 订阅

函数调用的工作流程  

 
 
  1. 传递参数:通过栈(_cdecl,_stdcall)或寄存器(_fastcall)。
  2. 函数调用:使用call指令调用函数,并将返回地址压入栈中。
  3. 保存栈底:将调用方的栈底寄存器ebp压栈。
  4. 申请栈空间,并保存寄存器环境:根据局部变量的多少提升esp(add esp,n)来开辟栈空间,用于存放函数的局部变量(VC6.0中debug模式这部分空间初始化为0XCC),并将函数中要用到的寄存器压栈进行保护。
  5. 函数实现代码
  6. 恢复寄存器环境:还原栈中保存的寄存器信息
  7. 平衡栈空间:平衡局部变量使用的栈空间,即进行降低esp操作(sub esp,n)。
  8. 函数返回:从栈顶取出第二步保存的返回地址
    • _cdecl调用方式,执行ret;相当于pop eip;(调用方平衡参数占用的栈空间)
    • 非_cdecl调用方式,执行ret n;相当于pop eip; add esp, n;(add esp,n 用于平衡参数占用的栈空间)

栈的形成和关闭

栈是内存中一段特殊的内存空间,后进先出原则。使用push、pop指令进行入栈、出栈操作。esp为栈顶寄存器,ebp为栈底寄存器,栈是向下生长的,即往地址低处生长。若栈非空,则esp指示的地址比ebp指示的地址小;若esp指示地址比ebp指示地址大,则为空栈。在VC6.0中,栈中可寻址的数据有局部变量、函数返回地址、函数参数等。当进行函数调用时,系统会开辟此函数所需的栈空间,而函数调用结束时,将释放开辟的栈空间,这一过程称为栈平衡。

函数调用方式说明

 
 
  1. _cdecl: C/C++默认的调用方式,调用方平衡函数参数所使用的栈空间。在函数返回后执行add esp, x(其中x为所有参数所占用栈空间大小,以字节为单位
  2. _stdcall:被调方平衡函数参数所使用的栈空间,即在函数返回时使用ret n指令。不定参数的函数无法使用此种调用方式。
  3. _fastcall: 使用寄存器传递参数,只使用了ecx和edx,分别传递第一个和第二个参数(从左往右分别为第一个、第二个...),其他参数则用栈传递。此种方式调用效率最高。
  • _cdecl和_stdcall对比(VC6.0 Debug模式下)注意:在Release模式下_cdecl参数平衡时可能存在复写传播优化)
图(1)源码:_cdecl与_stdcall的对比——Debug模式
图(2) 上图源代码的反汇编代码
  • _fastcall调用方式(VC6.0 Debug模式下)
图(3) 源码 _fastcall示例
图(4)上图源代码的反汇编代码
如上所示,最后的ret 8语句便是被调用平衡参数所占用的栈空间。而在调用方call @ILT+15(ShowFast)(00401014)后不用平衡参数所用栈空间。

函数参数

    函数参数通过栈结构或寄存器进行传递,在C++代码中,传参顺序是自右向左依次入栈。参数也是函数中的变量。但与函数中局部变量的存放地址有些许不同。相对esp来说,局部变量存放地址偏移量为负数,而参数存放地址偏移量为正数。
    采用非引用形参,则push指令将形参复制到栈顶(即为实参的副本),与实参数据在两个不同的地址处,是独立存在的,因此对函数参数的修改,实际上是对当前保存在栈内参数中保存的值进行修改,与实参数据没有关系。所以说,在C/C++中,形参是实参的副本,对形参的修改不影响其实参。


函数返回值

    调用函数时,先将参数入栈,再执行call指令后,将返回地址压入栈中,调用完毕后执行ret指令,弹出返回地址到eip,并进行参数所用栈平衡操作。在VC中使用寄存器eax来保存返回值,若返回值大小打于4字节,则使用eax和edx来保存返回值。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值