__cdecl _stdcall 区别

本文详细解析了C++中的几种主要调用约定,包括__cdecl、__stdcall、__fastcall和_thiscall。解释了每种调用约定的特点,如参数传递方式、栈清理责任等,并探讨了它们的应用场景及跨语言通信的重要性。

MSDN原话

Place the __cdecl modifier before a variable or a function name. Because the C naming and calling conventions are the default, the only time you need to use __cdecl is when you have specified the /Gz (stdcall) or /Gr (fastcall) compiler option. The /Gd compiler option forces the __cdecl calling convention.

Example

In the following example, the compiler is instructed to use C naming and calling conventions for the system function:

// Example of the __cdecl keyword
_CRTIMP int __cdecl system(const char *);

大概意思是:

__cdecl 是默认的调用方式,所以只有在打开/Gz(stdcall)或/Gr(fastcall)开关时才需要加上,/Gd开关打开表示需要强制加上__cdecl

__cdecl和__stdcall都是函数调用规范(还有一个__fastcall),规定了参数出入栈的顺序和方法,如果只用VC编程的话可以不用关心,但是要在C++和Pascal等其他语言通信的时候就要注意了,只有用相同的方法才能够调用成功.另外,像printf这样接受可变个数参数的函数只有用cdecl才能够实现.

_cdecl 是C Declaration的缩写,表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数无需要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。

_stdcall 是Standard Call的缩写,是C++的标准调用方式:所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是 retn X,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。称为自动清栈。函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。

_fastcall 是编译器指定的快速调用方式。由于大多数的函数参数个数很少,使用堆栈传递比较费时。因此_fastcall通常规定将前两个(或若干个)参数由寄存器传递,其余参数还是通过堆栈传递。不同编译器编译的程序规定的寄存器不同。返回方式和_stdcall相当。

_thiscall 是为了解决类成员调用中this指针传递而规定的。_thiscall要求把this指针放在特定寄存器中,该寄存器由编译器决定。VC使用ecx,Borland的C++编译器使用eax。返回方式和_stdcall相当。

_fastcall 和 _thiscall涉及的寄存器由编译器决定,因此不能用作跨编译器的接口。所以Windows上的COM对象接口都定义为_stdcall调用方式。

C中不加说明默认函数为_cdecl方式(C中也只能用这种方式),C++也一样,但是默认的调用方式可以在IDE环境中设置。

带有可变参数的函数必须且只能使用_cdecl方式,例如下面的函数:

int printf(char * fmtStr, ...);

int scanf(char * fmtStr, ...);

<think>我们被要求解释__stdcall和__cdecl调用约定的区别。根据引用资料,我们可以总结如下: 1. **参数传递顺序**: - 两者都从右向左压栈参数(即最后一个参数最先入栈,第一个参数最后入栈)[^2][^4][^5]。 2. **栈清理责任**: - `__stdcall`:由被调用函数(callee)负责清理栈上的参数[^1][^2][^5]。 - `__cdecl`:由调用者(caller)负责清理栈上的参数[^1][^2][^4][^5]。 3. **命名修饰(Name Mangling)**: - `__stdcall`:在导出函数名时,编译器会添加下划线前缀,并后缀以`@`符号和参数总字节数(十进制)[^3]。例如,函数`int func(int a, double b)`使用`__stdcall`时,可能被修饰为`_func@12`(假设参数总大小为12字节)。 - `__cdecl`:在导出函数名时,编译器通常只添加下划线前缀(如`_func`)[^3][^4]。 4. **可变参数支持**: - `__cdecl`:支持可变参数函数(如`printf`),因为调用者知道传入参数的数量和大小,由调用者清理栈可以灵活处理[^1][^4]。 - `__stdcall`:不支持可变参数,因为被调用函数在编译时就需要确定参数大小以生成清理栈的代码[^1][^5]。 5. **使用场景**: - `__stdcall`:常用于Windows API,因为它减少了调用后的代码大小(清理栈的代码在被调用函数中只存在一份)[^1][^3]。 - `__cdecl`:是C/C++程序的默认调用约定,特别适合有可变参数的函数[^1][^3][^4]。 6. **性能考虑**: - `__stdcall`:由于清理栈的代码在被调用函数内部,每个调用点不需要重复清理代码,因此生成的可执行文件可能更小[^5]。 - `__cdecl`:每次调用后都需要调用者清理栈,因此代码量稍大,但支持可变参数,更灵活[^1][^5]。 **总结对比表**: | 特性 | `__stdcall` | `__cdecl` | |--------------|----------------------------------------|----------------------------------------| | **参数压栈顺序** | 从右向左 | 从右向左 | | **栈清理方** | 被调用函数清理 | 调用者清理 | | **命名修饰** | `_FunctionName@N`(N为参数总字节数) | `_FunctionName` | | **可变参数** | 不支持 | 支持 | | **默认情况** | Windows API的标准约定 | C/C++程序的默认约定 | | **代码大小** | 较小(清理代码在函数内部) | 较大(每次调用后都需清理) | **示例说明**: 考虑一个函数:`int Add(int a, int b)`,假设`int`为4字节,则参数总大小为8字节。 - 使用`__stdcall`时,函数被修饰为`_Add@8`,且在函数返回指令`ret`后会加上立即数8(即`ret 8`)来清理栈。 - 使用`__cdecl`时,函数被修饰为`_Add`,函数返回使用`ret`(无立即数),调用者在调用后需要执行`add esp, 8`来清理栈。 在汇编层面,调用`__stdcall`函数: ```assembly push 2 ; 参数b push 1 ; 参数a call _Add@8 ; 调用函数,返回后栈已清理 ``` 调用`__cdecl`函数: ```assembly push 2 push 1 call _Add add esp, 8 ; 调用者清理栈 ``` 因此,在需要与Windows API交互时(如编写DLL供其他语言调用),通常使用`__stdcall`;而在编写可变参数函数或与C/C++默认调用兼容时,使用`__cdecl`。 --- ### 相关问题 1. 在跨语言调用(如Fortran调用C++)时,如何确保调用约定匹配? 2. 为什么Windows API主要使用__stdcall调用约定? 3. 如何显式指定C++成员函数的调用约定? 4. 在C#中调用C++ DLL时,如何声明`__stdcall`和`__cdecl`函数? 5. 函数调用约定对递归函数有什么影响?</think>### __stdcall与__cdecl调用约定的核心区别 #### 1. **栈清理责任**(核心区别) - **`__stdcall`**: 由**被调用函数**清理栈参数 ```assembly ; 函数内部 ret 12 ; 自动清理12字节参数 ``` - **`__cdecl`**: 由**调用者**清理栈参数 ```assembly call _MyFunction add esp, 12 ; 调用者手动清理 ``` > 此差异导致`__cdecl`生成的代码体积更大[^5] #### 2. **命名修饰规则** - **`__stdcall`**: 修饰格式:`_函数名@参数字节数` 示例:`MyFunc(int, float)` → `_MyFunc@8`(4+4=8字节) - **`__cdecl`**: 修饰格式:`_函数名` 示例:`MyFunc` → `_MyFunc`[^3] #### 3. **参数传递顺序** 两者均采用**从右向左**的参数压栈顺序: ```assembly push param3 ; 最右参数先入栈 push param2 push param1 ; 最左参数最后入栈 call Function ``` #### 4. **适用场景** | 特性 | `__stdcall` | `__cdecl` | |--------------|------------------------------|-------------------------------| | **可变参数** | ❌ 不支持 (如`printf`) | ✅ 支持[^1][^4] | | **默认约定** | Windows API标准 | C/C++默认约定[^3] | | **代码体积** | 较小 (清理代码在函数内部) | 较大 (每次调用需清理代码) | | **典型应用** | 系统API、DLL导出函数 | 跨平台代码、可变参数函数 | #### 5. **二进制兼容性** - 当DLL导出函数使用`__stdcall`时,调用方**必须声明相同约定** - 混合使用会导致栈不平衡和崩溃: ```c // DLL导出(__stdcall) __declspec(dllexport) int __stdcall Add(int a, int b); // 错误调用(默认__cdecl) typedef int (*cdeclFunc)(int, int); // 崩溃! ``` #### 6. **平台差异** - **Windows API** 统一采用`__stdcall`(如`WINAPI`宏)[^3] ```c #define WINAPI __stdcall // Windows头文件定义 ``` - **Unix/Linux** 系统通常使用`__cdecl`约定 > 关键结论:选择约定需考虑 **参数可变性**、**二进制兼容性** 和 **调用栈管理责任**。Windows API交互强制使用`__stdcall`,可变参数函数必须使用`__cdecl`[^1][^5]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值