函数调用约定分类
函数调用约定(Calling Convention)是规定函数在栈上的参数传递和清理方式的约定。不同的编程语言和编译器可能使用不同的调用约定,主要有以下几种常见的函数调用约定:
StdCall(标准调用约定) :
- 参数由调用者在栈上顺序压入,由被调用函数负责清理栈。
- 函数名称在链接时由名称修饰器(name decoration)进行修饰,用于指定函数的参数数量和类型。可以支持函数重载
- Windows API 中的大部分函数使用标准调用约定。
Cdecl(C 调用约定) :
- 参数由调用者在栈上顺序压入,由调用者负责清理栈。
- 函数名称在链接时不进行修饰。不支持函数重载
- 在 C 和 C++ 中常用的默认调用约定。
FastCall(快速调用约定) :
- 将一些参数(通常是寄存器数量限制内的参数)存放在寄存器中传递,剩余的参数由调用者在栈上压入。
- 寄存器用于存储参数的寄存器数量和顺序由编译器约定。
- 函数名称在链接时不进行修饰。
- 可以提高函数调用的性能。
ThisCall(成员函数调用约定) :
- 参数由调用者在栈上顺序压入,但第一个参数(this 指针)通过寄存器传递。
- 其余参数由调用者负责清理栈。
- 常用于非静态成员函数,this 指针通过 ECX 寄存器传递。
Winapi(Windows API 调用约定) :
- 与 StdCall 相同,参数由调用者在栈上顺序压入,由被调用函数负责清理栈。
- 函数名称在链接时不进行修饰。
- 在 Windows API 中,Winapi 通常是 StdCall 的同义词。
Pascal(帕斯卡调用约定) :
- 参数由调用者在栈上逆序压入,由被调用函数负责清理栈。
- 函数名称在链接时不进行修饰。
- 常用于 Delphi 等编程语言。
选择适当的函数调用约定主要取决于编程语言、平台和编译器的要求。通常情况下,你无需显式指定函数的调用约定,编译器会根据语言和平台的规范自动选择合适的调用约定。但在某些情况下,例如与非托管代码交互或使用特定的编译器选项时,可能需要手动指定函数的调用约定。
顺序压入(Arguments Pushed in Order)和逆序压入(Arguments Pushed in Reverse Order)是函数调用过程中参数在栈上的压入顺序。
- 顺序压入:在顺序压入的调用约定中,函数参数按照从左到右的顺序依次压入栈中。也就是说,第一个参数被压入栈的位置最低,最后一个参数被压入栈的位置最高。这意味着栈的顶部是最后一个参数的位置。
例如,假设有一个函数
void MyFunction(int a, int b, int c)
,使用顺序压入的调用约定时,参数a
会被首先压入栈,然后是参数b
,最后是参数c
。在函数内部,通过访问栈上的相对偏移量,可以获取到相应的参数值。 - 逆序压入:在逆序压入的调用约定中,函数参数按照从右到左的顺序依次压入栈中。也就是说,第一个参数被压入栈的位置最高,最后一个参数被压入栈的位置最低。这意味着栈的顶部是第一个参数的位置。
例如,假设有一个函数
void MyFunction(int a, int b, int c)
,使用逆序压入的调用约定时,参数c
会被首先压入栈,然后是参数b
,最后是参数a
。在函数内部,通过访问栈上的相对偏移量,可以获取到相应的参数值。
顺序压入和逆序压入是两种不同的参数传递方式,而具体使用哪种方式取决于函数调用约定的规定。不同的编程语言、平台和编译器可能使用不同的参数压入顺序。因此,在编写和调用函数时,需要了解所使用的编程语言和调用约定的规范,以正确地传递参数。
在 C++ 中指定函数的调用约定
1 | __cdecl void MyFunction(int a, int b); |
在 C++ 源码中,可以使用一些特定的关键字和修饰符来表示一个函数是导出函数。具体的表示方式取决于所使用的编译器和平台。
1 |
|