函数压栈

 在编译器编译文件的时候,软件会根据程序本身的要求对函数作不同的压栈处理。有的压栈是按照从左到右进行压栈,有的压栈是按照从右到左进行压栈,有的不压栈、直接用寄存器代替,有的是需要被调用函数自身自己平衡堆栈。下面,我们就可以一个一个自己看看。首先,随便写一个函数,

[cpp]  view plain copy
  1. int add(int a, int b)  
  2. {  
  3.     return a + b;  
  4. }  

 

    (1)从右到左压栈

    _cdelc是编译器默认的一种压栈方式,在函数上定义不定义其实意义不大。不过,我们愿意贴上代码说明一下,

[cpp]  view plain copy
  1. int __cdecl add(int a, int b)  
  2. {  
  3.     return a + b;  
  4. }  

    下面,在main函数里面调用此add函数,我们看看对应的汇编是怎么处理的,

[cpp]  view plain copy
  1. 14:       int p = add(2, 3);  
  2. 00401068   push        3  
  3. 0040106A   push        2  
  4. 0040106C   call        @ILT+0(_add) (00401005)  
  5. 00401071   add         esp,8  
  6. 00401074   mov         dword ptr [ebp-4],eax  
  7. 15:       return 1;  
  8. 00401077   mov         eax,1  

    从上面的代码来看,3先压栈,然后是数据2,很明显的从右向左压栈。

 

    (2)从左向右压栈

    其实,在windows之前的编译器是支持从左向右进行压栈的,但是现在不支持了。比如说,如果你输入下面这段代码,

[cpp]  view plain copy
  1. int __pascal add(int a, int b)  
  2. {  
  3.     return a + b;  
  4. }  

    此时,编译器会给你贴上一个错误提示,error C4226: nonstandard extension used : '__pascal' is an obsolete keyword。提示说的很明白,__pascal是一个过时的关键字,现在不支持了。其实堆栈压栈从左向右、还是从右向左其实无所谓。但是如果遇到的函数是变参的话,那么此时就存在问题了。因为对于__pascal而言,最后一个参数不知道究竟是在ebp的哪个偏移位置了?

 

    (3)用寄存器代替压栈

    用寄存器代替数据压栈是arm、powerpc等cpu使用的比较多的一种方法。因为用寄存器代替压栈,主要是考虑到速度方面的原因。毕竟取数据、保存数据相比较寄存器操作还是非常耗时间的,其此就是这两种cpu的寄存器资源特别丰富。同样,首先我们要用__fastcall装饰一下函数,

[cpp]  view plain copy
  1. int __fastcall add(int a, int b)  
  2. {  
  3.     return a + b;  
  4. }  

    那接下来,我们看看调用的时候发生了什么变化,

[cpp]  view plain copy
  1. 14:       int p = add(2, 3);  
  2. 00401068   mov         edx,3  
  3. 0040106D   mov         ecx,2  
  4. 00401072   call        @ILT+10(_add) (0040100f)  
  5. 00401077   mov         dword ptr [ebp-4],eax  
  6. 15:       return 1;  
  7. 0040107A   mov         eax,1  

    和上面的压栈不同,这里用edx保存了数据3,用eax保存了数据eax。毕竟寄存器运算要比内存运算快得多。


    (4)被调用和自行进行压栈恢复
    __stdcall是我们这里讲到的最后一种压栈模式。在函数压栈的方面,他和__cdelc是一样的,但是关键就在add函数结束的位置发生了变化。首先,我们需要用__stdcall装饰了一下函数,

[cpp]  view plain copy
  1. int __stdcall add(int a, int b)  
  2. {  
  3.     return a + b;  
  4. }  

    那么此时函数汇编的时候,代码发生了变化呢?

[cpp]  view plain copy
  1. 7:    int __stdcall add(int a, int b)  
  2. 8:    {  
  3. 00401020   push        ebp  
  4. 00401021   mov         ebp,esp  
  5. 00401023   sub         esp,40h  
  6. 00401026   push        ebx  
  7. 00401027   push        esi  
  8. 00401028   push        edi  
  9. 00401029   lea         edi,[ebp-40h]  
  10. 0040102C   mov         ecx,10h  
  11. 00401031   mov         eax,0CCCCCCCCh  
  12. 00401036   rep stos    dword ptr [edi]  
  13. 9:        return a + b;  
  14. 00401038   mov         eax,dword ptr [ebp+8]  
  15. 0040103B   add         eax,dword ptr [ebp+0Ch]  
  16. 10:   }  
  17. 0040103E   pop         edi  
  18. 0040103F   pop         esi  
  19. 00401040   pop         ebx  
  20. 00401041   mov         esp,ebp  
  21. 00401043   pop         ebp  
  22. 00401044   ret         8  

    这里的汇编代码没有什么特别之处。但是最后一个ret 8是什么意思呢?其实因为之前有两个参数a和b,那么8就是这两个参数占有的空间。此时ret b事实上就是让ebp恢复到原来的空间。仅此而已。但是我们发现,可能这一步运行之后,ebp加了不是8,而是12,这又是为什么呢?因为还有4个字节的返回地址没有加上呢。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值