函数的调用方与被调用方对于函数如何调用须要一个明确的约定,这样的约定就叫作调用惯例。
一个调用惯例一般会规定如下几方面的内容:
1. 函数参数的传递顺序及方式
函数参数的传递有多种方式,常见的是通过栈传递。函数的调用方将参数压入栈中,函数自己再从栈中取出参数。对于有多个参数的函数,调用惯例需要约定函数调用方将参数压入栈的顺序:是从左到右,还是从右到左。还有些调用惯例也支持使用寄存器来传递参数。
2. 栈的维护方式
在参数被压入栈后,函数体被调用执行,此后需要将被压入栈的参数再全部弹出,以使得栈在函数调用前后保持一致。这个弹出工作可以由调用方完成,也可以由被调用函数来完成。
3. 名字修饰的策略
为了链接的时候能够对调用惯例进行区分,调用管理要对函数名进行修饰。不同的调用惯例也有不同的修饰策略。
下表列出几个常见的调用惯例:
调用惯例 | 出栈方 | 参数传递 | 名字修饰 |
cdecl | 调用方 | 从右至左的顺序压参数入栈 | 下划线+函数名 |
stdcall | 函数自身 | 从右至左的顺序压参数入栈 | 下划线+函数名+@+参数的字节数,如函数int func(int a, double b)的修饰名是_func@12 |
fastcall | 函数自身 | 头两个DWORD(4字节)类型或者占更少字节的参数被放入寄存器,其他剩下的参数按从右到左的顺序入栈 | @+函数名+@+参数的字节数 |
pascal | 函数自身 | 从左至右的顺序入栈 | 较复杂,参见pascal 文档 |
其中cdecl 是c 语言默认的调用惯例。
此外,C 语言的诸如printf() 这样支持可变参数的函数,也是依赖于cdecl 的从右向左传递参数从而得到实现的。