随想录(函数压栈)
来源:互联网 发布:linux用什么浏览器 编辑:程序博客网 时间:2024/05/23 12:10
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
在编译器编译文件的时候,软件会根据程序本身的要求对函数作不同的压栈处理。有的压栈是按照从左到右进行压栈,有的压栈是按照从右到左进行压栈,有的不压栈、直接用寄存器代替,有的是需要被调用函数自身自己平衡堆栈。下面,我们就可以一个一个自己看看。首先,随便写一个函数,
int add(int a, int b){return a + b;}
(1)从右到左压栈
_cdelc是编译器默认的一种压栈方式,在函数上定义不定义其实意义不大。不过,我们愿意贴上代码说明一下,
int __cdecl add(int a, int b){return a + b;}
下面,在main函数里面调用此add函数,我们看看对应的汇编是怎么处理的,
14: int p = add(2, 3);00401068 push 30040106A push 20040106C call @ILT+0(_add) (00401005)00401071 add esp,800401074 mov dword ptr [ebp-4],eax15: return 1;00401077 mov eax,1
从上面的代码来看,3先压栈,然后是数据2,很明显的从右向左压栈。
(2)从左向右压栈
其实,在windows之前的编译器是支持从左向右进行压栈的,但是现在不支持了。比如说,如果你输入下面这段代码,
int __pascal add(int a, int b){return a + b;}
此时,编译器会给你贴上一个错误提示,error C4226: nonstandard extension used : '__pascal' is an obsolete keyword。提示说的很明白,__pascal是一个过时的关键字,现在不支持了。其实堆栈压栈从左向右、还是从右向左其实无所谓。但是如果遇到的函数是变参的话,那么此时就存在问题了。因为对于__pascal而言,最后一个参数不知道究竟是在ebp的哪个偏移位置了?
(3)用寄存器代替压栈
用寄存器代替数据压栈是arm、powerpc等cpu使用的比较多的一种方法。因为用寄存器代替压栈,主要是考虑到速度方面的原因。毕竟取数据、保存数据相比较寄存器操作还是非常耗时间的,其此就是这两种cpu的寄存器资源特别丰富。同样,首先我们要用__fastcall装饰一下函数,
int __fastcall add(int a, int b){return a + b;}
那接下来,我们看看调用的时候发生了什么变化,
14: int p = add(2, 3);00401068 mov edx,30040106D mov ecx,200401072 call @ILT+10(_add) (0040100f)00401077 mov dword ptr [ebp-4],eax15: return 1;0040107A mov eax,1
和上面的压栈不同,这里用edx保存了数据3,用eax保存了数据eax。毕竟寄存器运算要比内存运算快得多。
(4)被调用和自行进行压栈恢复
__stdcall是我们这里讲到的最后一种压栈模式。在函数压栈的方面,他和__cdelc是一样的,但是关键就在add函数结束的位置发生了变化。首先,我们需要用__stdcall装饰了一下函数,
int __stdcall add(int a, int b){return a + b;}
那么此时函数汇编的时候,代码发生了变化呢?
7: int __stdcall add(int a, int b)8: {00401020 push ebp00401021 mov ebp,esp00401023 sub esp,40h00401026 push ebx00401027 push esi00401028 push edi00401029 lea edi,[ebp-40h]0040102C mov ecx,10h00401031 mov eax,0CCCCCCCCh00401036 rep stos dword ptr [edi]9: return a + b;00401038 mov eax,dword ptr [ebp+8]0040103B add eax,dword ptr [ebp+0Ch]10: }0040103E pop edi0040103F pop esi00401040 pop ebx00401041 mov esp,ebp00401043 pop ebp00401044 ret 8
这里的汇编代码没有什么特别之处。但是最后一个ret 8是什么意思呢?其实因为之前有两个参数a和b,那么8就是这两个参数占有的空间。此时ret b事实上就是让ebp恢复到原来的空间。仅此而已。但是我们发现,可能这一步运行之后,ebp加了不是8,而是12,这又是为什么呢?因为还有4个字节的返回地址没有加上呢。
- 随想录(函数压栈)
- 随想录(一)
- 随想录(单元测试)
- 随想录(再谈链表)
- 随想录(再谈链表)
- 随想录(用memmove函数代替strncpy函数)
- 随想录(由自定义打印函数想到的)
- 随想录(用好红黑树)
- 随想录(软件调试)
- 随想录(管理自己)
- 随想录(关于培训)
- 随想录(三言两语app)
- 随想录(关于核心技术)
- 随想录(关于ucore)
- 随想录(句柄泄漏)
- 随想录(cmake编译)
- 随想录(canvas学习)
- 随想录(git操作)
- mysql查看table的记录数目
- Eclipse开发jsp出现乱码问题
- ubuntu 防止ARP攻击
- 关于解决oracle登录:ora-12154:tns:无法解析指定的连接标识符
- js给关键字加亮加超链接
- 随想录(函数压栈)
- 测试管道的容量
- 启用和更改 FILESTREAM 设置
- 电脑
- Enterprise Library系列文章回顾与总结
- Linux 下的一个全新的性能测量和调式诊断工具 Systemtap,第 1 部分: kprobe
- msm7x27A LCDC timing设置
- wp访问资源文件的两种方式
- 如何在android 4.0.3中添加系统服务