PASCAL、_stdcall、_cdecl 调用方式区别与比较

调用约定(Calling Convention)是指在程序设计语言中为了实现函数调用而建立的一种协议。这种协议规定了该语言的函数中的参数传送方式、参数是否可变和由谁来处理堆栈等问题。不同的语言定义了不同的调用约定。

 

在C++中,为了允许操作符重载和函数重载,C++编译器往往按照某种规则改写每一个入口点的符号名,以便允许同一个名字(具有不同的参数类型或者是不同的作用域)有多个用法,而不会打破现有的基于C的链接器。这项技术通常被称为名称改编(Name Mangling)或者名称修饰(Name Decoration)。许多C++编译器厂商选择了自己的名称修饰方案。

 

因此,为了使其它语言编写的模块(如Visual Basic应用程序、Pascal或Fortran的应用程序等)可以调用C/C++编写的DLL的函数,必须使用正确的调用约定来导出函数,并且不要让编译器对要导出的函数进行任何名称修饰。

看在VS中一些函数的原型:

typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM); // 窗口过程函数在VS的定义

int PASCAL FAR WSAAsyncSelect( _In_ SOCKET s, _In_ HWND hWnd,

 _In_ u_int wMsg, _In_ long lEvent); // 异步选择函数在VS的定义

int PASCAL FAR WSACleanup(void);    // SOCKET清理函数在VS的定义

C约定规定参数传递顺序是从右到左,即最右边的参数最先压栈,由调用者恢复堆栈指针

PASCAL约定和C约定正好相反,它规定参数是从左向右传递,由被调用者恢复堆栈

STDCALL是C约定和PASCAL约定的混合体,它规定参数的传递是从右到左,恢复堆栈的工作交由被调用者完成。Win32只用STDCALL约定, 但除了一个特例, 即: wsprintf。

__stdcall 这是一种函数调用方式。 __stdcall方式函数的参数压栈顺序从右到左,是Pascal 缺省调用方式,通常用于win32 API中,自己在退出时清空栈

__stdcall将参数压栈是按C语言的顺序(从右到左),但与C语言不同的是它是由被调用者将参数从栈中清除,所以它的编译文件比_cdecl小。

__stdcall是Windows  API函数中默认的调用约定,VB、VFP等也采用这个约定。

__cdecl是C语言采用的默认调用方法,对于传送参数的内存栈却是由调用者来维护的。实现可变参数的调用只能用该方法。是MFC的缺省调用参数。

__fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。

  调用一个函数时,参数的传递是由堆栈来完成的,PASCAL的惯例是参数从左向右压入堆栈,堆占的指针的还原可由被调用的程序本身来负责,这样还原堆栈指针的代码在被调用的函数中,此代码(还原指针)只有一份。而C的惯例是参数入栈是从右向左,堆占的指针的还原可由调用的程序本身来负责,每调用一次,就生成一份堆占的指针的还原的代码,所以调用越多,程序代码就越长,执行效率就越差。(调用几次函数其代码中只会有一份的函数的代码)。这里的代码是指编译连接后的机器代码。

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

C调用约定(即用__cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。   

WINDOWS的函数调用时需要用到栈(stack,一种先入后出的存储结构)。当函数调用完成后,栈需要清除,这里就是问题的关键,如何清除??如果我们的函数使用了_cdecl,那么栈的清除工作是由调用者,用COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。

如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨(开发)平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现,在WINDEF.H中的这样的宏定义:

#define CALLBACK _stdcall

#define WINAPI _stdcall

#define PASCAL _stdcall

#define WINAPIV _cdecl

在演化过程中曾经出现的PASCAL、CALLBACK、WINAPI、APIENTRY都表示了同样的意义_stdcall调用习惯。

那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用_cdecl。到这里我们有一个结论,如果程序中没有涉及可变参数,最好使用__stdcall关键字

__stdcall 就是 我叫你来吃饭,吃了饭,自己洗碗

__cdecl 就是 我叫你来吃饭,吃了饭,我来帮你洗碗


发表评论

电子邮件地址不会被公开。 必填项已用*标注