__cdecl与__stdcall的核心区别在于栈清理责任:__cdecl由调用方清栈,__stdcall由被调函数清栈;两者压栈顺序均为从右向左,但名字修饰不同(__cdecl为\_Func,__stdcall为\_Func@N),影响链接与动态调用。

Windows平台下C++函数调用约定主要有 __cdecl、__stdcall、__fastcall、__thiscall 和 __vectorcall(较新版本VC支持)。其中最基础、最常被问及的是 __cdecl 与 __stdcall,它们的核心差异在于谁来清理栈空间和参数压栈顺序——而这直接关系到函数能否被正确调用、链接和导出。
参数压栈顺序:两者完全一致
__cdecl 和 __stdcall 都采用从右向左的压栈顺序。例如:
func(a, b, c);
实际入栈顺序是:c → b → a。这点没有区别,不必担心调用时参数错位。
栈平衡责任:关键区别所在
函数调用后,栈指针(ESP)必须恢复到调用前位置,这个“清理栈”的动作由谁完成,决定了调用约定的本质差异:
立即学习“C++免费学习笔记(深入)”;
-
__cdecl:由调用方(caller)负责清栈。编译器在每次调用后生成
add esp, N指令(N为参数总字节数)。 -
__stdcall:由被调用函数(callee)负责清栈,即函数返回前执行
ret N(带立即数的 ret 指令),自动修正栈顶。
这意味着:同一个函数如果声明为 __stdcall 却被按 __cdecl 方式调用(或反之),会导致栈失衡——轻则局部变量错乱,重则程序崩溃,且问题往往延迟暴露,极难调试。
名字修饰(Name Mangling)规则不同
为了支持函数重载和调用约定识别,编译器会对函数名进行修饰。两者典型规则如下(以32位x86为例):
-
__cdecl:函数名前加一个下划线,如
_MyFunc;若带参数,不体现参数大小,仍为_MyFunc。 -
__stdcall:函数名前加下划线,**后缀加 @ + 参数总字节数**,如三个 int 参数(12字节)→
_MyFunc@12。
因此,在 .def 文件导出、GetProcAddress 动态获取、或混用 C/C++ 与汇编时,必须严格匹配调用约定,否则链接器报 “unresolved external” 或运行时报 “procedure not found”。
适用场景与默认行为
- __cdecl 是 C/C++ 默认调用约定(除非显式指定),尤其适合可变参数函数(如 printf),因为只有调用方知道实际传了多少参数,必须由它清栈。
- __stdcall 主要用于 Windows API 函数(如 MessageBoxA、CreateWindowEx),也是 COM 接口的标准约定。它减少调用方代码体积(无需每处都清栈),利于接口统一。
- __fastcall 尝试用寄存器(ECX、EDX)传前两个DWORD参数,其余仍压栈;__thiscall 是非静态成员函数的默认约定(this 指针通常放 ECX)。
基本上就这些。理解它们不是为了手写汇编,而是读懂链接错误、排查崩溃、正确使用 Win32 API 或编写 DLL 导出函数时的关键底层支撑。










