调用惯例一般包括以下三方面的内容:
· 函数参数的传递顺序和方式
· 栈的维护方式
· 名字修饰(Name-mangling)的策略
在C语言里,存在着多个调用惯例,而默认的调用惯例是:cdecl。任何一个没有显示指定调用惯例的函数都默认是cdecl惯例。对于函数:int foo(int n, float m)而言,它的完整形式是:
int _cdecl foo(int n, float m) (MSVC编译器)
此外,还存在很多别的调用惯例:cdecl,stdcall,fastcall,pascal四种调用惯例,之间的具体区别如下表:
调用惯例 | 出栈方 | 参数传递 | 名字修饰 |
cdecl | 函数调用方 | 从右至左的顺序压参数入栈 | 下划线+函数名 |
stdcall | 函数本身 | 从右至左的顺序压参数入栈 | 下划线+函数名+@+参数的字节数,如函数int func(int a, double b)的修饰名是_func@12 |
fastcall | 函数本身 | 头两个DWORD(4字节)类型或占更少字节的参数被放入寄存器,其他剩下的参数按从右到左的顺序压入栈 | @+函数名+@+参数的字节数 |
pascal | 函数本身 | 从左至右的顺序压参数入栈 | 较为复杂, 参见pascal文档 |
此外,不少编译器还提供一种称为naked call的调用惯例, 这种调用惯例在特殊的场合,其特点是编译器不产生任何保护寄存器的代码,故称故称为naked call。
对于C++语言,以上几种调用惯例的名字修饰策略都有所改变(C++有自己更加复杂的名字修饰策略)。