函数调用约定
- 作者: 刘杰
- 来源: 技术那些事
- 阅读:217
- 发布: 2025-06-13 20:28
- 最后更新: 2025-06-30 10:44
概念
调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。介绍 C/C++ 中的三种函数调用约定。
调用规范
__cdecl 约定
__cdecl 函数调用约定是我们最长见的一种约定,我们平时在写程序的时候默认会使用该种约定,其特点如下:
- 参数从右向左依次传递,存放在堆栈中。
- 堆栈平衡由调用函数来维护。
- C语言编译时的函数命名规则为
下划线+名称:__functionName
堆栈平衡是为了维护函数调用前后堆栈的状态来进行的一系列操作,具体解释请自行了解。
__stdcall 约定
__stdcall 约定是我们在写 WinAPI 的时候经常用的约定,很多 windows下面的 API 都是该种调用约定,其特点如下:
- 参数从右向左依次传递,存放在堆栈中。
- 堆栈平衡由被调用函数来维护
- C语言编译时的函数命名规则为
下划线+名称+@+参数字节大小:__functionName@len
本约定在写 WinAPI 的时候经常用到,和 __cdecl 的却别在于堆栈平衡由函数自身进行维护。
__fastcall 约定
__fastcall 约定从名称上可以看出是速度快,因为其参数是可以放在寄存器中传递的,通常需要在要求高效率的函数中使用此约定,其特点如下:
- 最右侧两个参数由
ecx和edx两个寄存器来传递,剩余参数从右往左依次存放在堆栈中。 - 堆栈平衡由被调用函数来维护
- C 语言编译时的函数命名规则为
@+函数名+@+参数大小:@functionName@len
该约定是高效率的调用约定,和另外两种约定最大的区别就是参数的传递方式,利用了寄存器来快速的传递。
C++编译时的函数名规则
C++ 和 C 语言的命名规则是不同的,C++ 要比 C 语言复杂的多
汇编角度看调用约定
调用约定指的是在函数被调用的时候,会按照不同的规则,翻译成不同的汇编代码。
调用栈
当一个函数被调用时,首先会将返回地址压入堆栈,紧接着会将函数的参数以此压入堆栈。当函数退出时候,会以相反的顺序依次退出堆栈。因此,函数在被调用前和调用后的堆栈保持平衡。

C语言调用约定
C语言调用约定,要求在声明函数时用 __cdecl对函数进行修饰,例如:
c
void __cdecl Foo(int a, int b);
C语言调用会在目标(Object)文件中,产生一个符号来代表这个函数,此符号的形式为下划线 + 函数名,且函数以 ret 形式返回。例如:
c
Foo(0x12345678, 0x11223344);
展开成汇编代码如下:
c
push 0x11223344h // 从右往左第一个参数
push 0x12345678h // 从右往左第二个参数
call _Foo // 调用函数
add esp, 8 // 平衡堆栈
从右至左,将参数推进堆栈,执行结束后,以 ret 返回。此时的堆栈和调用前的堆栈并不一致,需要“调用者”来恢复堆栈,用 add 指令将堆栈恢复平衡。
注意:C语言或者C++语言在编译成汇编代码时候,符号名会对变量名和函数名进行替换,替换成一个内存上的地址。由于地址不容易分辨,所以用常量来代替。例如,函数 Foo 编译成汇编代码就会变成 _Foo 或者 _Foo@8。
标准调用约定
标准调用约定,要求在函数声明时,用 __stdcall对函数进行修饰,例如:
c++
void __stdcall Foo(int a, int b);
C语言调用会在目标文件中产生一个符号来代表这个函数,此符号形式为下划线 + 函数名 + X。其中 X 代表清理堆栈时候需要的数字,函数以 ret X 形式返回。例如:
c
Foo(0x12345678, 0x11223344);
展成汇编代码如下:
c
push 0x11223344
push 0x12345678
call _Foo@8
从左至右,将参数依次压入堆栈,当函数调用完,函数以 ret 8 返回函数。Foo 函数负责恢复堆栈,而“调用者”不负责恢复堆栈,这个是C语言调用和标准调用最终要的区别之一。
在VC编译器中,默认使用 C 语言的调用约定。而 windows 驱动程序的编写中,需要使用标准调用约定,尤其是入口函数。系统会寻找 _DriverEntry@8 作为程序的入口点。如果用 C语言的调用约定,会将 DriverEntry ,编译成 _DriverEntry ,而不是 _DriverEntry@8,那么就会导致连接错误。因此在编译驱动时候,需要改变默认的编译调用约定。