C++中cdecl、stdcall、fastcall、thiscall异同——10分钟弄清
以下是 C++ 中四种常见调用约定(cdecl
、stdcall
、fastcall
、thiscall
)的对比说明,结合示例和场景分析帮助理解:
cdecl
(C 声明约定)
- 参数传递:从右向左压栈(如
func(a, b)
先压b
再压a
) - 清栈方:调用者清理栈(调用后需执行
add esp, X
) - 适用场景:C/C++ 默认约定,支持可变参数(如
printf
) - 汇编特征:函数名前加
_
(如_func
) - 示例:
int __cdecl add(int a, int b) { return a + b; } // 调用后编译器生成:add esp, 8 (清理两个 int 参数)
stdcall
(标准调用约定)
- 参数传递:从右向左压栈(同
cdecl
) - 清栈方:被调函数自己清栈(函数末尾用
ret X
指令) - 适用场景:Windows API 标准约定(如
MessageBox
) - 汇编特征:函数名格式为
_func@X
(X
为参数总字节数) - 示例:
int __stdcall sub(int a, int b) { return a - b; } // 函数返回时执行:ret 8 (自动清理参数)
fastcall
(快速调用约定)
- 参数传递:
- 前两个参数通过寄存器传递(
ECX
和EDX
) - 剩余参数从右向左压栈
- 前两个参数通过寄存器传递(
- 清栈方:被调函数清理栈(同
stdcall
) - 适用场景:性能敏感场景(小型参数函数)
- 汇编特征:函数名前加
@
(如@func@8
) - 示例:
int __fastcall mul(int a, int b) { return a * b; } // 编译后:a 存入 ECX,b 存入 EDX,无需压栈
thiscall
(C++ 成员函数约定)
- 参数传递:
this
指针通过ECX
寄存器传递- 其他参数从右向左压栈
- 清栈方:被调函数清理栈(同
stdcall
) - 适用场景:C++ 类成员函数的默认约定(不可显式指定)
- 汇编特征:无特殊修饰,但隐含
this
指针 - 示例:
class Calculator { public:int __thiscall div(int b) { return value / b; } // 编译器自动添加 __thiscall }; // 调用时:this 存入 ECX,参数 b 压栈
对比总结
特性 | cdecl | stdcall | fastcall | thiscall |
---|---|---|---|---|
参数顺序 | 从右向左压栈 | 从右向左压栈 | 寄存器 + 从右向左压栈 | this (ECX)+ 从右向左压栈 |
清栈方 | 调用者 (add esp, X ) | 被调函数 (ret X ) | 被调函数 (ret X ) | 被调函数 (ret X ) |
适用场景 | 可变参数函数 | Windows API | 高频调用小函数 | C++ 类成员函数 |
函数修饰 | _func | _func@8 | @func@8 | 无特殊修饰 |
关键差异示例
// cdecl:调用者清栈
sum = __cdecl add(1, 2); // 汇编:push 2; push 1; call _add; add esp, 8 // stdcall:函数自己清栈
result = MessageBox(nullptr, "Hello", "Title", MB_OK); // 汇编:call _MessageBoxA@16 // thiscall:隐含 this 传递
Calculator calc;
calc.div(3); // 汇编:mov ecx, &calc; push 3; call ?div@Calculator@@QAEHH@Z
实际行为可能因编译器优化略有差异,调试时观察寄存器/栈变化可验证调用约定。