_stdcall介绍

合集下载

__stdcall用例 -回复

__stdcall用例 -回复

__stdcall用例-回复1. 什么是__stdcall?__stdcall是一种在应用程序开发中常见的函数调用约定,用于规定函数调用时参数的传递方式和返回值的返回方式。

它主要用于C和C++编程语言中,是一种Windows特定的函数调用约定。

2. __stdcall与其他函数调用约定有什么区别?__stdcall与其他函数调用约定,如__cdecl和__fastcall等,主要区别在于参数传递的方式和函数栈的清理方式上。

- __stdcall约定规定参数是从右向左依次压入堆栈,调用者负责在调用函数后清理堆栈。

- __cdecl约定规定参数是从右向左依次压入堆栈,但是堆栈的清理工作由被调用函数自己负责。

这使得__cdecl可以在不同编译器和库之间进行交互,但是会增加函数调用的开销。

- __fastcall约定将前两个整型参数存储在寄存器中,能够提高函数调用的性能。

但是这种约定只适用于少量的参数,过多的参数会导致其它参数被压入堆栈。

3. 为什么在Windows编程中常使用__stdcall?在Windows平台上,许多API函数使用了__stdcall约定,开发者需要遵守这个约定来正确调用这些函数。

这主要是为了确保编译器和函数库之间的兼容性,使得函数调用的参数传递和堆栈清理都能够正确执行。

此外,__stdcall约定还有以下优势:- 由于堆栈的清理工作由调用者来完成,可以确保在函数调用结束后堆栈的正确恢复,避免发生堆栈溢出等问题。

- 由于参数是从右向左依次压入堆栈,可以有效地避免参数溢出问题。

- 使用__stdcall约定的函数可以通过函数原型或函数指针的方式传递给其他函数,使得函数调用更加灵活和方便。

4. 如何定义一个使用__stdcall的函数?使用__stdcall约定定义一个函数,需要在函数声明的返回类型前加上__stdcall关键字。

例如:c__stdcall void MyFunction(int a, int b);在函数定义时也需要加上__stdcall关键字,例如:c__stdcall void MyFunction(int a, int b) {函数体}5. __stdcall约定可以有哪些限制?__stdcall约定在参数传递过程中有一些限制:- 参数必须是固定长度的类型,例如整型、指针等。

_stdcall,_cdecl与extern C

_stdcall,_cdecl与extern C

_stdcall,_cdecl与extern "C"调用一个函数时,总是先把参数压入栈,然后通过call指令转移到被调用函数,在完成调用后清除堆栈.这里有两个问题:(1)哪个参数先入栈(2)由谁来清理堆栈.这两个方面的问题称为"调用约定(Calling Conventi ons)"问题.这里只讨论_stdcall和_cdecl调用约定,前者是Windows API函数常用的调用约定,后者即是C调用约定._stcall:参数按从右向左的顺序入栈,由被调用函数清理堆栈._cdecl :参数按从右向左的顺序入栈,由调用函数清理堆栈.为了防止函数名冲突,或者重载的需要(c++程序),编译器通常都会修改用户定义的函数名.这样说也许太抽象了,还是来看看具体的例子吧(通常代码最能说明问题,也最能把问题说清楚).来看一段简单的代码:void swap(int *x,int *y){int temp;temp=*x;*x=*y;*y=temp;}很简单,即实现两个数相交换,学过c语言的人对这段代码应该很熟悉.再来加一段测试代码:int main(int argc, char* argv[]){int a=4,b=5;swap(&a,&b);printf("a=%d,b=%d\n",a,b);return0;}以下的测试基于VC++6.0.(1)swap函数不加任何修饰(最简单的情况)来看看它们相应的汇编代码://main调用swap的汇编代码swap(&a,&b);00401096 8D 45 F8 lea eax,[ebp-8]00401099 50 push eax0040109A 8D 4D FC lea ecx,[ebp-4]0040109D 51 push ecx0040109E E8 62 FF FF FF call @ILT+0(swap) (00401005) 004010A3 83 C4 08 add esp,8//swap的汇编代码void swap(int *x,int *y){00401020 55 push ebp00401021 8B EC mov ebp,esp00401023 83 EC 44 sub esp,44h00401026 53 push ebx00401027 56 push esi00401028 57 push edi00401029 8D 7D BC lea edi,[ebp-44h]0040102C B9 11 00 00 00 mov ecx,11h00401031 B8 CC CC CC CC mov eax,0CCCCCCCCh 00401036 F3 AB rep stos dword ptr [edi]int temp;temp=*x;00401038 8B 45 08 mov eax,dword ptr [ebp+8] 0040103B 8B 08 mov ecx,dword ptr [eax] 0040103D 89 4D FC mov dword ptr [ebp-4],ecx *x=*y;00401040 8B 55 08 mov edx,dword ptr [ebp+8] 00401043 8B 45 0C mov eax,dword ptr [ebp+0Ch] 00401046 8B 08 mov ecx,dword ptr [eax] 00401048 89 0A mov dword ptr [edx],ecx *y=temp;0040104A 8B 55 0C mov edx,dword ptr [ebp+0Ch] 0040104D 8B 45 FC mov eax,dword ptr [ebp-4] 00401050 89 02 mov dword ptr [edx],eax}00401052 5F pop edi00401053 5E pop esi00401054 5B pop ebx00401055 8B E5 mov esp,ebp00401057 5D pop ebp00401058 C3 ret@ILT+0(?swap@@YAXPAH0@Z):00401005 E9 16 00 00 00 jmp swap (00401020)swap被编译器修改为 :?swap@@YAXPAH0@Z,这是c++转换方式. (2)_stdcallswap(&a,&b);004010E6 8D 45 F8 lea eax,[ebp-8]004010E9 50 push eax004010EA 8D 4D FC lea ecx,[ebp-4]004010ED 51 push ecx004010EE E8 12 FF FF FF call @ILT+0(swap) (00401005) void _stdcall swap(int *x,int *y){00401030 55 push ebp00401031 8B EC mov ebp,esp00401033 83 EC 44 sub esp,44h00401036 53 push ebx00401037 56 push esi00401038 57 push edi00401039 8D 7D BC lea edi,[ebp-44h]0040103C B9 11 00 00 00 mov ecx,11h00401041 B8 CC CC CC CC mov eax,0CCCCCCCCh 00401046 F3 AB rep stos dword ptr [edi]int temp;temp=*x;00401048 8B 45 08 mov eax,dword ptr [ebp+8] 0040104B 8B 08 mov ecx,dword ptr [eax] 0040104D 89 4D FC mov dword ptr [ebp-4],ecx *x=*y;00401050 8B 55 08 mov edx,dword ptr [ebp+8] 00401053 8B 45 0C mov eax,dword ptr [ebp+0Ch] 00401056 8B 08 mov ecx,dword ptr [eax] 00401058 89 0A mov dword ptr [edx],ecx *y=temp;0040105A 8B 55 0C mov edx,dword ptr [ebp+0Ch] 0040105D 8B 45 FC mov eax,dword ptr [ebp-4] 00401060 89 02 mov dword ptr [edx],eax}00401062 5F pop edi00401063 5E pop esi00401064 5B pop ebx00401065 8B E5 mov esp,ebp00401067 5D pop ebp00401068 C2 08 00 ret 8@ILT+0(?swap@@YGXPAH0@Z):00401005 E9 16 00 00 00 jmp swap (00401020)(3)_cdeclvoid _cdecl swap(int *x,int *y)与第一种情况相同,即这是vc++默认的方式.(4)extern "C" _cdeclvoid extern "C" _cdecl swap(int *x,int *y)swap(&a,&b);00401096 8D 45 F8 lea eax,[ebp-8]00401099 50 push eax0040109A 8D 4D FC lea ecx,[ebp-4]0040109D 51 push ecx0040109E E8 67 FF FF FF call @ILT+5(_swap) (0040100a) 004010A3 83 C4 08 add esp,8@ILT+5(_swap):0040100A E9 11 00 00 00 jmp swap (00401020)swap被编译器修改为_swap,即加一个下划线(c语言转换方式),其余与第三种情况相同.(5)extern "C" _stdcallvoid extern "C" _stdcall swap(int *x,int *y)swap(&a,&b);00401096 8D 45 F8 lea eax,[ebp-8]00401099 50 push eax0040109A 8D 4D FC lea ecx,[ebp-4]0040109D 51 push ecx0040109E E8 62 FF FF FF call @ILT+0(_swap@8) (00401005)swap被修改为_swap@8,即加一个下划线,后面加上参数在堆栈中所占的字节数(c语言转换方式),其余与第二种情况相同.。

_stdcall与_cdel

_stdcall与_cdel

1._cdecl(1). 是C Declaration的缩写,表示C语言默认的函数调用方法,实际上也是C++的默认的函数调用方法。

(2). 所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。

具体所示:调用方的函数调用->被调用函数的执行->被调用函数的结果返回->调用方清除调整堆栈。

(3). 被调用函数无需要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。

总的来说函数的参数个数可变的(就像printf函数一样),因为只有调用者才知道它传给被调用函数几个参数,才能在调用结束时适当地调整堆栈。

(4). 因为每个调用的地方都需要生成一段调整堆栈的代码,所以最后生成的文件较大。

2._stdcall(CALLBACK/WINAPI)(1). 是Standard Call的缩写,要想函数按照此调用方式必须在函数名加入_stdcall,通常_win32 api 应该是_stdcall调用规则。

通过VC++编写的DLL欲被其他语言编写的程序调用,应将函数的调用方式声明为_stdcall 方式,WINAPI都采用这种方式。

(2). 所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。

具体所示:调用方的函数调用->被调用函数的执行-> 被调用方清除调整堆栈->被调用函数的结果返回。

(3). 这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是retn X,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。

称为自动清栈。

(4). 函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。

总的来说,就是函数的参数个数不能是可变的。

是从_cdecl 修改而来, _stdcall 不支持可变参数,并且清栈由被调用者负责,其他的都一样(5). 因为只需在被调用函数的地方生成一段调整堆栈的代码,所以最后生成的文件较小。

__stdcall用法

__stdcall用法

__stdcall用法stdcall是Windows系统中常用的一个调用约定,它定义了函数调用参数、函数调用栈和函数返回值的传递方式。

由于其高效稳定的特性,stdcall被广泛应用于多种不同的编程语言,包括汇编语言、C/C++和Visual Basic等等。

stdcall的特点可以概括为:1.数的参数书写顺序从右至左,表示参数在栈中的压入顺序。

2.于cdecl调用,由调用者管理函数调用栈的清理,对于stdcall 来说,则由被调用的函数负责清理函数调用栈。

3.于cdecl调用,参数从左至右依次传入,而对于stdcall调用,参数从右至左传递。

4.于stdcall调用,传入参数在调用前就已经存储在栈中,而不是在调用时动态压入栈中。

stdcall在编程汇编过程中是非常有用的,因为它可以为程序员提供一个简单易用的接口,帮助程序员更方便地调用接口函数。

stdcall用于以下几种情况:1.序调用库函数:stdcall具有较高的稳定性,很好地实现了库函数的调用。

2.序调用操作系统函数:由于系统函数运行在内核态,因此要求调用参数和函数返回值传递都有较高的稳定性,stdcall恰恰满足了这种需求。

3.汇编函数和高级语言结合起来:由于stdcall可以简化函数参数的传递,因此在汇编与高级语言结合的情况下,使用stdcall可以大大简化程序的书写,减少出现错误的可能性。

stdcall调用有其自身的优势,因此在现在的软件开发中,它被广泛使用,特别是对于调用Win32库函数或操作系统函数的情况,它可以让程序员更方便地调用函数,让程序更稳定地运行,减少出现Runtime Error的概率。

总之,stdcall用法为程序调用提供了一个非常安全可靠的参数传递机制,它可以大大提高函数的调用效率,有助于降低程序中出现Runtime Error的概率,而且还可以简化汇编程序的书写。

因此,stdcall用法正成为越来越多的开发者所采用的一种编程调用方式。

stdcall调用约定分析,以c语言为分析对象。

stdcall调用约定分析,以c语言为分析对象。

stdcall调用约定分析,以c语言为分析对象。

学习各种高级外挂制作技术,马上去百度搜索"魔鬼作坊",点击第一个站进入,快速成为做挂达人。

stdcall调用约定分析,以c语言为分析对象(一)-、编译平台本文中C代码的编译平台为VC6.0,编译版本为Debug版本。

二、研究的C代码int__stdcall stdcallFun(int Num1,int Num2){if(Num1>Num2)return Num1;elsereturn Num2;}void main(int argc,char*argv[]){int x1,x2,nBigger;x1=5;x2=6;nBigger=stdcallFun(x1,x2);return;}三、利用VC调试时,查看Debug版本的汇编代码(如看不懂该部分代码可直接跳到第四点的分析部分)stdcallFun函数stdcallFun:004010A0push ebp;保存ebp,004010A1mov ebp,esp004010A3sub esp,40h;stdcallFun分配临时变量004010A6push ebx004010A7push esi004010A8push edi;保护现场004010A9lea edi,[ebp-40h]004010AC mov ecx,10h004010B1mov eax,0CCCCCCCCh004010B6rep stos dword ptr[edi];初始化临时变量004010B8mov eax,dword ptr[ebp+8];mov eax,Num1004010BB cmp eax,dword ptr[ebp+0Ch]cpm Num1,Num2004010BE jle stdcallFun+25h(004010c5)004010C0mov eax,dword ptr[ebp+8]004010C3jmp stdcallFun+28h(004010c8)004010C5mov eax,dword ptr[ebp+0Ch]004010C8pop edi004010C9pop esi004010CA pop ebx004010CB mov esp,ebp004010CD pop ebp004010CE ret8main函数main:004010EC mov ecx,13h004010F1mov eax,0CCCCCCCCh004010F6rep stos dword ptr[edi]004010F8mov dword ptr[ebp-4],5;x1=5004010FF mov dword ptr[ebp-8],6;x2=6 00401106mov eax,dword ptr[ebp-8]00401109push eax;变量x2的值入栈0040110A mov ecx,dword ptr[ebp-4]0040110D push ecx;变更x1的值入栈0040110E call@ILT+25(_stdcallFun@8)(0040101e) 00401113mov dword ptr[ebp-0Ch],eax00401116pop edi00401117pop esi00401118pop ebx00401119add esp,4Ch0040111C cmp ebp,esp0040111E call__chkesp(00401280);调试代码00401123mov esp,ebp00401125pop ebp00401126ret四、阶段分析1.理解stdcall参数传递规则的关键是C语言的变量内存分配规则。

调用约定(Calling convention)详解(__stdcall,__cdecl,__fastcall)

调用约定(Calling convention)详解(__stdcall,__cdecl,__fastcall)

调用约定(Calling convention)详解(__stdcall,__cdecl,__fastcall)分类:c/c++/vc 2010-03-12 10:21 233人阅读评论(1) 收藏举报#define CALLBACK __stdcall#define WINAPI __stdcall#define WINAPIV __cdecl#define APIENTRY WINAPI#define APIPRIVATE __stdcall#define PASCAL __stdcall调用约定(Calling convention):决定函数参数传送时入栈和出栈的顺序,由调用者还是被调用者把参薯弹出栈,以及编译器用来识别函数名字的修饰约定。

函数调用约定有多种,这里简单说一下:1、__stdcall调用约定相当于16位动态库中经常使用的PASCAL调用约定。

在32位的VC++5.0中PASCAL调用约定不再被支持(实际上它已被定义为__stdcall。

除了__pascal外,__fortran和__syscall也不被支持),取而代之的是__stdcall调用约定。

两者实质上是一致的,即函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分(关于函数名的修饰部分在后面将详细说明)。

_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。

VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。

2、C调用约定(即用__cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。

对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。

另外,在函数名修饰约定方面也有所不同。

_cdecl是C和C++程序的缺省调用方式。

__stdcall用法

__stdcall用法

__stdcall用法stdcall(“standardcall”的缩写)是一种函数调用约定,指明函数原型中参数的传递和函数返回值的接受方式。

stdcall由微软公司制定的,使用 stdcall程序可以兼容所有的Windows操作系统,所以使用 stdcall函数很常见。

1.本原理stdcall的基本原理是在调用者的栈中,从右向左进行参数的入栈,在被调用函数结束时,调用者栈中的参数由被调用函数负责出栈和释放,而被调用函数由调用者负责负责分配空间以保存返回值及其他作用。

stdcall是一种调用约定,它具有如下优点:(1)参数的传递方式比较简单,函数的调用者只需从右向左入栈即可,而不需要考虑参数的顺序问题;(2)在被调用函数结束时,被调用函数负责出栈,函数的调用者则不需要考虑参数出栈问题;(3)由于调用参数从右向左压栈,所以避免了压栈顺序与实际参数顺序不一致的问题;(4)函数的调用者只需要将返回值存放到一块固定的空间,被调用函数返回时会将返回值写入该空间,从而避免了由于传递参数顺序错误而出现的错误;(5)stdcall中的调用参数一般以堆栈的形式传递,减少了参数传递所需的内存空间,从而降低函数调用的开销;(6)采用stdcall有利于对函数的传输,从而缩短函数的调用时间。

2. stdcall的实现stdcall的实现需要遵守以下几个步骤:(1)首先,函数的调用者需要建立一个调用堆栈,在堆栈中将所需要传递的参数从右向左入栈;(2)接着,函数的调用者需要将调用地址压入堆栈,以指示函数调用的位置;(3)当所有的参数都入栈完毕后,函数的调用者需要调用CPU 指令“call”,以跳转到被调用函数中;(4)被调用函数执行完毕后,会将所有调用者栈上的参数出栈;(5)最后,被调用函数在返回时,会将函数的返回值写入一块固定的内存空间,此时函数的调用者可以将返回值读取出来。

3. stdcall的应用stdcall在Windows系统中用于实现程序与系统库函数之间的交互,也即实现dll文件的调用。

__declspec 和 DLL导出函数

__declspec 和 DLL导出函数

__cdecl和__stdcall都是函数调用规范(还有一个__fastcall),规定了参数出入栈的顺序和方法,如果只用VC编程的话可以不用关心,但是要在C++和Pascal等其他语言通信的时候就要注意了,只有用相同的方法才能够调用成功.另外,像printf这样接受可变个数参数的函数只有用cdecl才能够实现.__declspec主要是用于说明DLL的引出函数的,在某些情况下__declspec(dllexport)在DLL中生命引出函数,比用传统的DEF文件方便一些.在普通程序中也可以用__declspec(dllimport)说明函数是位于另一个DLL中的导出函数.例子不太好举啊,其实就是在函数声明的时候多加一个关键字,比如很多API函数就是象这样声明的:int WINAPI MessageBoxA(HWND,LPCSTR,LPSTR,UINT);而WINAPI实际上就是__stdcall.大多数API都采用__stdcall调用规范,这是因为几乎所有的语言都支持__stdcall调用.相比之下,__cdecl只有在C语言中才能用.但是__cdecl调用有一个特点,就是能够实现可变参数的函数调用,比如printf,这用__stdcall调用是不可能的.__fastcall这种调用规范比较少见,但是在Borland C++ Builder中比较多的采用了这种调用方式.如果有共享代码的需要,比如写DLL,推荐的方法是用__stdcall调用,因为这样适用范围最广.如果是C++语言写的代码供Delphi这样的语言调用就必须声明为__stdcall,因为Pascal不支持cdecl调用(或许Delphi的最新版本能够支持也说不定,这个我不太清楚).在其他一些地方,比如写COM组件,几乎都用的是stdcall调用.在VC或Delphi或C++Builder里面都可以从项目设置中更改默认的函数调用规范,当然你也可以在函数声明的时候加入__stdcall,__cdecl,__fastcall关键字来明确的指示本函数用哪种调用规范.__declspec一般都是用来声明DLL中的导出函数.这个关键字也有一些其他的用法,不过非常罕见.关于DLL的函数:动态链接库中定义有两种函数:导出函数(export function)和内部函数(internal function)。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

stdcall调用约定:
stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。

在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。

stdcall调用约定声明的语法为(以前文的那个函数为例):
int __stdcall function(int a,int b)
stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。

以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处
翻译成汇编语言将变成:
push 2 第二个参数入栈
push 1 第一个参数入栈
call function 调用参数,注意此时自动把cs:eip入栈
而对于函数自身,则可以翻译为:
push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复mov ebp,esp 保存堆栈指针mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a
add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b
mov esp,ebp 恢复esp
pop ebp
ret 8
而在编译时,这个函数的名字被翻译成_function@8
注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。

其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。

从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。

函数结束后,ret 8表示清理8个字节的堆栈,函数自己恢复了堆栈。

cdecl调用约定:
cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:
int function (int a ,int b) //不加修饰就是C调用约定
int __cdecl function(int a,int b)//明确指出C调用约定在写本文时,出乎我的意料,发现cdecl调用约定的参数压栈顺序是和stdcall是一样的,参数首先由有向左压入堆栈。

所不同的是,函数本身不清理堆栈,调用者负责清理堆栈。

由于这种变化,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。

对于前面的function函数,使用cdecl后的汇编码变成:
调用处
push 1
push 2
call function
add esp,8 注意:这里调用者在恢复堆栈
被调用函数_function处push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复
mov ebp,esp 保存堆栈指针
mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a
add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b
mov esp,ebp 恢复esp
pop ebp
ret 注意,这里没有修改堆栈
MSDN中说,该修饰自动在函数名前加前导的下划线,因此函数名在符号表中被记录为_function,但是我在编译时似乎没有看到这种变化。

由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来,就可以使用不定参数,例如对于CRT中的sprintf函数,定义为:
int sprintf(char* buffer,const char* format,...)
由于所有的不定参数都可以通过format确定,因此使用不定个数的参数是没有问题的。

fastcall
fastcall调用约定和stdcall类似,它意味着:
函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过
从右向左的顺序压栈
被调用函数清理堆栈
函数名修改规则同stdcall
其声明语法为:int fastcall function(int a,int b)
thiscall
thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。

它是C++类成
员函数缺省的调用约定。

由于成员函数调用还有一个this指针,因此必须特殊处理,th
iscall意味着:
参数从右向左入栈
如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this 指针
在所有参数压栈后被压入堆栈。

对参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈
为了说明这个调用约定,定义如下类和使用代码:
class A
{
public:
int function1(int a,int b);
int function2(int a,...);
};
int A::function1 (int a,int b)
{
return a+b;
}
#i nclude
int A::function2(int a,...)
{
va_list ap;
va_start(ap,a);
int i;
int result = 0;
for(i = 0 i < a i ++)
{
result += va_arg(ap,int);
}
return result;
}
void callee()
{
A a;
a.function1 (1,2);
a.function2(3,1,2,3);
}
callee函数被翻译成汇编后就变成:
//函数function1调用
0401C1D push 2
00401C1F push 1
00401C21 lea ecx,[ebp-8]
00401C24 call function1 注意,这里this没有被入栈//函数function2调用
00401C29 push 3
00401C2B push 2
00401C2D push 1
00401C2F push 3
00401C31 lea eax,[ebp-8] 这里引入this指针
00401C34 push eax
00401C35 call function2
00401C3A add esp,14h
可见,对于参数个数固定情况下,它类似于stdcall,不定时则类似cdecl
naked call
这是一个很少见的调用约定,一般程序设计者建议不要使用。

编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返回返回值,只能用插入汇编返回结果。

这一般用于实模式驱动程序设计,假设定义一个求和的加法程序,可以定义为:__declspec(naked) int add(int a,int b)
{
__asm mov eax,a
__asm add eax,b
__asm ret
}
注意,这个函数没有显式的return返回值,返回通过修改eax寄存器实现,而且连退出函数的ret指令都必须显式插入。

上面代码被翻译成汇编以后变成:
mov eax,[ebp+8]
add eax,[ebp+12]
ret 8
注意这个修饰是和__stdcall及cdecl结合使用的,前面是它和cdecl结合使用的代码,对于和stdcall结合的代码,则变成:
__declspec(naked) int __stdcall function(int a,int b)
{
__asm mov eax,a
__asm add eax,b
__asm ret 8 //注意后面的8
}
至于这种函数被调用,则和普通的cdecl及stdcall调用函数一致。

函数调用约定导致的常见问题如果定义的约定和使用的约定不一致,则将导致堆栈被破坏,导致严重问题,下面是两种常见的问题:
函数原型声明和函数体定义不一致
DLL导入函数时声明了不同的函数约定
以后者为例,假设我们在dll种声明了一种函数为:
__declspec(dllexport) int func(int a,int b);//注意,这里没有stdcall,使用的是
cdecl
使用时代码为:
typedef int (*WINAPI DLLFUNC)func(int a,int b);
hLib = LoadLibrary(...);
DLLFUNC func = (DLLFUNC)GetProcAddress(...)//这里修改了调用约定
result = func(1,2);//导致错误
由于调用者没有理解WINAPI的含义错误的增加了这个修饰,上述代码必然导致堆栈被破坏,MFC在编译时插入的checkesp函数将告诉你,堆栈被破坏了。

相关文档
最新文档