__stdcall由被调用方清理栈,__cdecl由调用方清理栈;两者均从右向左压参,但名字修饰不同(__cdecl为_func@0,__stdcall为_func@12),且__stdcall不支持可变参数,多用于Windows API。

__stdcall 和 __cdecl 的核心区别在谁清理栈
调用约定本质是函数调用时参数入栈顺序、栈清理责任、名字修饰规则的组合。最直接的差异在于:__cdecl 由调用方(caller)清理栈,__stdcall 由被调用方(callee)清理栈。
-
__cdecl是 C/C++ 默认调用约定,支持可变参数函数(如printf),因为只有调用方知道传了多少个参数,才能安全清栈 -
__stdcall常用于 Windows API(如CreateWindowEx、MessageBox),要求参数个数固定,函数自己负责弹出所有参数 - 两者都从右向左压参(
func(a, b, c)→ 先压c,再b,最后a),这点一致
名字修饰(name mangling)不同导致链接失败
编译器会根据调用约定对函数名做不同修饰,不匹配就会报 LNK2019: unresolved external symbol。
-
__cdecl函数修饰为_funcname@0(例如_myfunc@0),前面加下划线,后面@后是字节数(32 位下指参数总大小) -
__stdcall函数修饰为_funcname@12(例如_myfunc@12),同样带下划线和@,但数值是实际参数字节数(如三个int就是 12) - 如果头文件声明用
__stdcall,而实现文件没写或写成__cdecl,链接器根本找不到符号
混用场景下必须显式声明,尤其调用 Windows API 或回调函数
Windows SDK 头文件已用 WINAPI(即 __stdcall)宏定义了大量函数。若你手写函数指针类型或实现回调,不匹配就 crash 或参数错乱。
- 注册窗口过程时,
WNDPROC是LRESULT (CALLBACK*)(HWND, UINT, WPARAM, LPARAM),其中CALLBACK就是__stdcall;你写的函数必须加__stdcall,否则栈失衡 - 调用
LoadLibrary+GetProcAddress获取 API 地址时,函数指针类型必须与导出约定一致,比如:typedef int (__stdcall *PFN_MessageBox)(HWND, LPCWSTR, LPCWSTR, UINT);
漏掉__stdcall就会调用失败 - 在 C++ 类成员函数中实现回调?不行——成员函数隐含
this指针,调用约定再匹配也过不了 ABI,得用静态函数或 lambda + captureless +__stdcall(C++17 起支持)
64 位 Windows 下二者已无实质区别
AMD64/x64 平台只有一种调用约定(Microsoft x64 calling convention),__stdcall、__cdecl、__fastcall 全部被忽略,编译器直接无视这些关键字。
立即学习“C++免费学习笔记(深入)”;
- 所有非浮点参数优先走寄存器(RCX、RDX、R8、R9),多余参数才入栈
- 栈清理统一由调用方负责,且必须 16 字节对齐
- 所以你在 x64 项目里写
void __stdcall foo()和void __cdecl foo(),生成的汇编完全一样 - 但跨平台或需兼容 32 位 DLL 时,仍要严格区分——很多老驱动、COM 组件、资源 DLL 还跑在 x86 上
__stdcall,只要用了 Windows SDK 中的函数类型定义(比如 WNDPROC、DLGPROC、HOOKPROC),你就已经深陷调用约定约束之中。栈清理权一旦错配,不会立刻报错,而是表现为参数读取错位、返回值异常、或者某次调用后堆栈损坏——这种 bug 往往只在特定优化等级或特定输入下复现。










