我们最常用的调用约定有以下2种,__cdecl和__stdcall, __cdecl 是c/c++的默认调用约定(calling convention), __stdcall是windows api 函数的调用约定。这2种调用约定的参数传递方式是一样的, 都是从右至左; 在堆栈的维护方式上, __cdecl要求调用者清除堆栈, 而__stdcall由被调用函数自己清除堆栈;名称修饰上,__cdecl直接在原有的函数名称上加一个下划线_,而__stdcall方式不仅加下划线而且还在后面加一个“@参数占用字节数”,具体如何,请看下面的例子。
#include <stdio.h>
int __cdecl funcA(int a, int b);
int __stdcall funcB(int a, int b);
int main()
{
int a, b, c, d;
a = 3;
b = 4;
c = funcA(a, b);
d = funcB(a, b);
return 0;
}
int __cdecl funcA(int a, int b)
{
return a + b;
}
int __stdcall funcB(int a, int b)
{
return a + b;
}
上面2个函数funcA和funcB, 一个采用__cdecl调用方式, 一个采用__stdcall调用方式,在main函数中,我们来调用这2个函数, 看看产生的汇编代码有什么不同:
11: int a, b, c, d;
12:
13: a = 3;
00401048 mov dword ptr [ebp-4],3
14: b = 4;
0040104F mov dword ptr [ebp-8],4
15:
16: c = funcA(a, b); // __cdecl 调用方式
00401056 mov eax,dword ptr [ebp-8]
00401059 push eax
0040105A mov ecx,dword ptr [ebp-4]
0040105D push ecx
0040105E call @ILT+0(_funcA) (00401005)
00401063 add esp,8 // 由调用者main平衡堆栈
00401066 mov dword ptr [ebp-0Ch],eax
17: d = funcB(a, b); // __stdcall调用方式
00401069 mov edx,dword ptr [ebp-8]
0040106C push edx
0040106D mov eax,dword ptr [ebp-4]
00401070 push eax
00401071 call @ILT+10(_funcB@8) (0040100f)
00401076 mov dword ptr [ebp-10h],eax
18:
19: return 0;
00401079 xor eax,eax
堆栈:
|
……
|
a
|
Ebp - 4
|
b
|
Ebp – 8
|
c
|
Ebp – 0Ch
|
d
|
Ebp - 10h
|
|
…..
|
由汇编代码可以看出,程序定义的四个local变量a,b,c,d分别对应于栈空间的ebp-4,
ebp-8, ebp-0Ch, ebp-10h
程序中a=3, 这个赋值语句对应的汇编代码是
mov dword ptr[ebp-4], 3
同样,赋值语句b=4, 对应的汇编代码是
mov dword ptr[ebp-8], 4
函数funcA调用后的返回值保存在c中,
mov dword ptr[ebp-0Ch], eax
函数funcB调用后的返回值保存在d中,
mov dword ptr[ebp-10h], eax
现在看看__cdecl调用方式的特点:
从参数传递上来看:
c=funcA(a, b);
对应汇编:
mov eax, dword ptr[ebp-8]
push eax ;压入参数b
mov ecx, dword ptr[ebp-4]
push ecx ;压入参数a
从这里的汇编可以很明显的看出, 先传入参数b,然后传入参数a, 即参数传递由右到左
从堆栈清除来看:
call @ILT+0(_funcA) (00401005)
add esp,8
函数funcA调用之后,main函数又用了一句代码add esp, 8来恢复堆栈, 即__cdecl调用方式是由调用者清除堆栈的
从函数修饰来看:
call @ILT+0(_funcA) (00401005)
从这里可以看出,原有的函数funcA被修改成了_funcA,即__cdecl调用方式是直接在原有的函数前面加一个下划线来修饰的。
接着,我们来看看__stdcall方式调用的特点:
d = funcB(a, b);
从参数传递来看:
mov edx,dword ptr [ebp-8]
push edx ;传入参数b
mov eax,dword ptr [ebp-4]
push eax ;传入参数a
很明显,跟__cdecl调用方式一样,参数都是从右到左的
从清除堆栈来看:
call @ILT+10(_funcB@8) (0040100f)
函数调用之后,并没有任何清除堆栈的操作,而堆栈肯定是要保持平衡的,主函数main里面没有清除堆栈的操作,那么也就意味着堆栈的清除是由被调用函数自己做的, 即__stdcall调用方式,由被调用函数自己清除堆栈
从函数修饰来看:
call @ILT+10(_funcB@8) (0040100f)
函数被修改成了_funcB@8, 即修饰后的函数是原有的函数的前面加上了下划线,后面加上参数所占空间大小,中间用@隔开。因为有2个参数int a, int b,而sizeof(int)=4, 2个加起来等于8。 如果是这样int funcB(int a, double 8), 则修饰后的函数是_funcB@12, 因为sizeof(int)+sizeeof(double)=4+8=12。
把__cdecl调用方式和__stdcall调用方式用表格总结一下:
调用方式
|
参数传递
|
堆栈清除
|
函数修饰
|
__cdecl
|
从右到左
|
调用者清除堆栈
|
funcA(int a, int b); =>_funcA
|
__stdcall
|
从右到左
|
被调用者自己清除堆栈
|
funcB(int a, int b);
=> _funcB@8
|
__cdecl因为是由调用者清除堆栈的,故可以使用可变参数,最明显的一个例子就是printf函数,采用的就是__cdecl调用方式, 同时由于__cdecl是调用者清除堆栈,所以,每当调用一个函数,都会增加清除堆栈的代码,故产生的最终代码要比__stdcall方式要大,占用磁盘空间也大。
原为地址:http://blog.chinaunix.net/u/5391/showart_1772654.html
分享到:
相关推荐
- **返回值**:与 __cdecl 和 __stdcall 类似,返回值通常存储在 EAX 寄存器中。 - **修饰名格式**:函数名修饰为 `@functionname@number` 形式。 #### 应用场景: - **性能敏感的代码**:当函数调用频繁且对性能有...
关于函数调用方式__stdcall和__cdecl详解 __stdcall __cdecl 两者的相同点与不同点 实例 __stdcall __stdcall的全称是standard call。是C++的标准调用方式。 函数参数的入栈顺序为从右到左入栈。函数返回时使用retn ...
__stdcall、__cdecl和__fastcall各有优势和应用场景,选择合适的调用约定对于优化代码性能和提高程序的可维护性至关重要。了解这些调用约定的具体细节有助于开发者更好地理解底层机制,并能够根据项目需求做出最佳...
_stdcall、_cdecl和_fastcall 的区别.zip
本文将深入探讨两种常见的调用约定:`__stdcall` 和 `__cdecl`,这两种约定在C++和Windows API编程中尤其常见。 首先,`__cdecl` 是C Declaration的缩写,它是C语言默认的函数调用约定。在`__cdecl`约定中,参数按...
`__cdecl`是C语言的调用约定,它指定函数参数由被调用者清理。在这里,`int __cdecl sprintf()`意味着这个函数使用`cdecl`调用约定,并返回一个整数值。`sprintf`函数的作用是将格式化的数据写入指定的缓冲区,类似...
### MFC中_stdcall调用约定 在C和C++编程中,调用约定(call convention)定义了如何在函数调用期间传递参数、清理栈空间等规则。...通过本文的介绍,希望能帮助读者更好地理解和应用`_stdcall`调用约定。
易语言cdecl回调处理源码,cdecl回调处理,stdcall_to_cdecl,stdcall_to_cdecl_free,回调函数,test,VirtualAlloc,VirtualFree,set_data
这个错误表明在链接阶段发现了一个重复定义的`operator delete`函数,`__cdecl`是函数调用约定,表示参数由被调用者清理。`operator delete`是C++标准库中的一个成员,用于动态内存的释放。 在描述中虽然没有提供...
根据给定的文件信息,我们可以深入探讨C/C++中几种主要的函数调用方式:__cdecl、__stdcall和__fastcall,以及C++特有的thiscall。 ### __cdecl `__cdecl`,即“C声明”或“C default”,是C/C++中默认的调用约定...
stdcall调用约定是默认的调用约定,但也可以使用cdecl调用约定。 2. CharSet字段:该字段用于控制字符串参数的传递方式。如果CharSet字段设置为Unicode,则字符串参数将被转换为Unicode字符열;如果CharSet字段设置...
这种方式使得 __stdcall 调用约定在性能上通常优于 __cdecl,特别是在大量调用时。此外,__stdcall 调用约定下的函数名通常会经过编译器特定的修饰,因此,它在进行跨语言(如C++与汇编)调用时,需要注意名称修饰的...
`__stdcall`是C++中的一个调用约定,它规定了函数参数的传递方式和谁负责清理栈。 在`__stdcall`调用约定中,函数的参数是从右向左压入栈的,而函数本身负责清理参数。这种约定通常用于Windows API函数,因为它允许...
本篇文章主要讨论两种常见的调用约定:stdcall和cdecl,这两种约定在C/C++编程中尤其常见,尤其是在Windows平台上。 1. **stdcall调用约定** stdcall是Windows API广泛使用的调用约定。在stdcall约定中,**函数的...
"stdcall介绍" stdcall 调用约定是高级语言中的一种函数调用约定,用于解决函数调用时参数传递问题。...stdcall 调用约定和 cdecl 调用约定都是高级语言中常用的函数调用约定,它们的特点和使用方法都是非常不同的。
如果不匹配,可能需要使用`typedef`来定义新的类型,或者使用`__stdcall`和`__cdecl`等关键字来指定调用约定。 4. **错误处理和资源释放**:在调用DLL函数后,记得检查返回值以处理可能出现的错误。同时,别忘了...
在上面的代码中,`f_stdcall`和`f_cdeclr`分别代表了使用`__stdcall`和`__cdecl`调用约定的函数。我们可以通过汇编语言的视角来看待这两种调用方式的差异: - 对于`__stdcall`: - 在进入函数之前,首先保存`ebp`...
### stdcall 与 cdecl:函数调用方式详解 在编程领域,特别是在C和C++语言中,函数调用约定(Calling Convention)是一个重要的概念。它规定了如何传递参数、谁来清理栈空间以及如何处理返回值等细节。本文将详细...