前情提要
这次的这篇关于函数约定及返回值的博客,并没写的很精髓,如果看到了就只做参考吧。这方面的知识还是推荐看《程序员的自我修养》。
本文中有关于函数调用堆栈的内容可先浏览博客函数调用堆栈过程详解
一:函数调用约定
调用约定所规定的
1)函数参数的传递顺序和方式
这一项其实说的就是函数产生的参数入栈顺序会不同
对于C中的三个约定_cdecl _stdcall _fastcall 都是从右到左的入栈顺序,因为C语言满足类似于printf等可变参函数。而对于pascal这种不支持可变参的函数,_pascal 约定的入栈顺序就为从左到右。
但需要注意的是 其中_fastcall调用约定有一些不同,fast意为快速,这个约定就是一个快速调用约定,其快速的原理其实很简单,它利用了两个寄存器来将8字节以内的数据直接带入被调函数,大于8字节的剩余数据依旧按照从右往左的顺序以入栈的方式传递参数。
参考一下图:
栈的维护方式
这一条主要项规定清理形参内存的角色,即在函数调用完成之后从被调函数返回主调函数时,需要清理主调函数栈顶的形参,而这几种约定就规定了谁来执行这个动作。
1) _cdecl:主调函数清理
伪代码:(假设形参为两个int 类型数据)
ret // 被调函数返回指令
//下面是主调函数
call sum //函数调用
add esp,8 //调用完成之后将esp+8 ,esp为栈顶指针,加 8就意为将栈的空间减小8个字节,
//即两个int 类型的数据空间。这样就达到了清理形参的目的。
注意:ret 是在被调函数中的最后一行代码,下面的两行是在主调函数的代码,所以_cdecl约定是规定在主调函数中清理形参内存。
2)_stdcall:被调函数自己清理
伪代码:(假设形参为两个int 类型数据)
ret 8 //被调函数中的返回指令,但其后多了一个8,这条语句执行了两个动作:
call sum//主调函数中的函数调用语句
ret有以下作业
1.pop下一条指令入pc寄存器
2.执行esp += 8.即跟上面一样,清理了形参
**注意:**ret 是在被调函数中的,该句执行完了之后就返回到主调函数了,并且此时已经将形参清理了,所以_stdcall是规定被调函数自己清理形参。
3)_fastcall:被调函数自己清理
首先在上面的叙述中我们知道这个约定所规定的的传参方式是用寄存器传递,大于8字节就压栈。
伪代码:(假设形参为三个int 类型数据)
ret 4//在参数字节小于8时,没有在主函数栈内压栈所以无需回退栈桢
//但这里有三个int类型数据,所以需要像_stdcall方式一样自己清理形参
//但只需要清理一个4字节就可以了
//在被调函数开头下多了这两句,这两句就是小于8字节的寄存器传参
mov dword ptr[ebp-8],edx
mov dword ptr[ebp-4].ecx
call sum//主调函数调用语句
3.名字修饰的策略
这一项规定其实就是规定了函数产生的符号格式。
1)_cdecl:下划线+函数名
2)_stdcall:下划线+函数名+@+参数的字节数。
e.g:int func(int a,double b)
的修饰名为 _func@12
3)_fastcall:@+函数名+@+参数的字节数
4)_pascal:较为复杂,参见pascal文档
二:函数的返回值
一:知识储备
- 寄存器
寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC)。在中央处理器的算术及逻辑部件中,存器有累加器(ACC)。
寄存器是内存阶层中的最顶端,也是系统获得操作资料的最快速途径。
以32位CPU为例,有8个寄存器,分别是:
eax ebx
ecx edx
esp ebp //栈桢寄存器
esi edi - 在了解了寄存器之后,我们来直接看代码
1)字节数 <= 4 时
伪代码:
int func() //被调函数
{
001413C0 push ebp
001413C1 mov ebp,esp
·
·中间省略