stdcall和cdecl
三月 20, 2022 [c++] #c++stdcall和cdecl
概念
-
stdcall调用 stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,由调用者完成压栈操作,被调函数自身在返回前清空堆栈。 WIN32 Api都采用_stdcall调用方式,这样的宏定义说明了问题: WINAPI _stdcall
按C编译方式,stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionname@number. 所以int func( int a, double b ) 描述为: _func@12.
-
cdecl调用 cdecl是C/C++的缺省调用方式,参数采用从右到左的压栈方式,由调用者完成压栈操作 ,传送参数的内存栈由调用者维护。 cedcl约定的函数只能被C/C++调用,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用stdcall函数的大。 按C编译方式,cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。
区别
几乎我们写的每一个WINDOWS API函数都是stdcall类型的,首先,需要了解两者之间的区别: WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。 当函数调用完成后,栈需要清除,这里就是问题的关键,如何清除?如果我们的函数使用了cdecl,那么栈的清除工作是由调用者,用COM的术语来讲就是客户来完成的。 这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。 如果使用stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨(开发) 平台的调用中,我们都使用stdcall(虽然有时是以WINAPI的样子出现)。 那么为什么还需要cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行, 因此,这种情况我们只能使用 cdecl。到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用stdcall关键字。
编译时函数名修饰约定规则
1. 首先介绍一下函数修饰名,C或者C++函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的,
如在模块定义文件里头指定输出C++重载函数、构造函数、析构函数,又如在汇编代码里调用C或C++函数等。 修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。名字修饰约定随调用约定和编译种类(C或C++)的不同而变化。 函数名修饰约定随编译种类和调用约定的不同而不同。下面详细说明在C++编译器中修饰名的约定方法。
2. C++编译时函数名修饰约定规则
stdcall调用约定:
1)、以"?"标识函数名的开始,后跟函数名;
2)、函数名后面以"@@YG"标识参数表的开始,后跟参数表;
3)、参数表以代号表示:
X--void ,
D--char,
E--unsigned char,
F--short,
H--int,
I--unsigned int,
J--long,
K--unsigned long,
M--float,
N--double,
_N--bool,
PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以"0"代替,一个"0"代表一次重复;
4)、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
5)、参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"Z"标识结束。
其格式为?functionname@@YG*@Z"或"?functionname@@YG*XZ,例如 int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z” void Test2() -----“?Test2@@YGXXZ”
cdecl调用约定:
规则同上面的stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。
在C++编译时,引入c库里面的函数的处理
如果Add(int a, int b)是在c语言编译器编译,而在c++文件使用,由上知, 因为c编译器和c++编译器对函数名的解释不一样(c++编译器解释函数名的时候要考虑函数参数,这样是了方便函数重载,而在c语言中不存在函数重载的问题),会出现链接错误。 哪么该如何处理呢?这是关键字extern就派上用场了。而在c++文件使用c编译器编译过的函数, 则需要在c++文件中声明:extern "C" Add(int a, int b),使用extern "C",实质就是告诉c++编译器,该函数是c库里面的函数。请保持我的名称,不要给我生成用于链接的函数修饰名。 另外顺便说一下extern的另一个用途,extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。