随笔 - 20, 文章 - 0, 评论 - 45, 引用 - 0
数据加载中……

函数调用的区别:_cdecl以及_stdcall

一、概念
1)_stdcall调用
   _stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,由调用者完成压栈操作,被调函数自身在返回前清空堆栈。
   WIN32 Api都采用_stdcall调用方式,这样的宏定义说明了问题:  #define WINAPI _stdcall
  
   按C编译方式,_stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionname@number.
   Therefore, the function declared as int func( int a, double b ) is decorated as follows: _func@12.
   按C++编译方式,可参看(三)

 2)_cdecl调用
  
_cdecl是C/C++的缺省调用方式,参数采用从右到左的压栈方式,由调用者完成压栈操作 ,传送参数的内存栈由调用者维护
   _cedcl约定的函数只能被C/C++调用
,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。
   按C编译方式,_cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。
   按C++编译方式,可参看(三)

二、区别
     几乎我们写的每一个WINDOWS API函数都是__stdcall类型的,首先,需要了解两者之间的区别: WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。当函数调用完成后,栈需要清除,这里就是问题的关键,如何清除?如果我 们的函数使用了_cdecl,那么栈的清除工作是由调用者,用COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题,不同的编译器产生栈的方式不 尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨(开发) 平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。那么为什么还需要_cdecl呢?当我们遇到这样的函数如 fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用 _cdecl。到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用__stdcall关键字。

三、编译时函数名修饰约定规则
1)首先介绍一下函数修饰名,C” 或者“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可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。#inlcude "***.***"     和 int   f()  等价于   extern int f() ;即 extern 申明的函数前不需要加引用头文件就能用。

参考文献:
博客:http://www.cnblogs.com/Winston/archive/2008/09/11/1289391.html










posted on 2011-04-19 13:23 Kenny Jiang 阅读(9805) 评论(12)  编辑 收藏 引用 所属分类: C++

评论

# re: 函数调用的区别:_cdecl以及_stdcall  回复  更多评论   

其实,从设计角度上说,还是觉得__cdecl 合理些,谁创建谁销毁
2011-04-19 15:30 | 溪流

# re: 函数调用的区别:_cdecl以及_stdcall  回复  更多评论   

@溪流
_cdecl是由调用者销毁的。不同的编译器产生栈的方式不尽相同,所以在跨语言时是不适用的。
2011-04-19 17:57 | Kenny Jiang

# re: 函数调用的区别:_cdecl以及_stdcall  回复  更多评论   

@Kenny Jiang
这怎么说?压栈工作难道不是主调函数做的?
2011-04-19 22:54 | 溪流

# re: 函数调用的区别:_cdecl以及_stdcall  回复  更多评论   

说得 乱七八糟 完全不对 什么叫不同编译器产生栈不同 我没见过..都是内存 没有不同的 大部分语言都可以指定调用方式 只要知道调用约定 _stdcall和_cdecl 没区别 使用 _stdcall只是默认规则而已 另外 Pascal的默认调用约定是_fastcall 最新版本的vs 则更加乱 ecx esi edi eax edx 都会拿来传参
2011-04-20 02:53 | Lo

# re: 函数调用的区别:_cdecl以及_stdcall  回复  更多评论   

@Lo
额,我们的程序能够正常运行都是编译器的功劳,每种语言都有自己的编译器,而且不止一种,如c++有gcc,vs等。他们的约定方式不同,所以如果用vs调用gcc编译产生的库,那么vs可能不能正确释放gcc的栈,所以跨平台时,建议是函数本身释放这些空间,而不是调用者,即_stdcall方式。
2011-04-20 12:43 | Kenny Jiang

# re: 函数调用的区别:_cdecl以及_stdcall  回复  更多评论   

@溪流
你可以详细看看博客,这个是有区别的。
2011-04-20 12:47 | Kenny Jiang

# re: 函数调用的区别:_cdecl以及_stdcall  回复  更多评论   

@Lo
fastcall不就是拿寄存器来传的。不过到了64位下面,stdcall啊cdecl啊这些乱七八糟的东西都被取消了,全部做成fastcall。但是你stdcall仍然可以使用,就是会被当成fastcall来编译了。

而且stdcall啊cdecl啊fastcall啊都是跟编译器无关的协议,不会受到不同编译器的影响的。不然gcc怎么调用windows api?
2011-04-20 13:21 | 陈梓瀚(vczh)

# re: 函数调用的区别:_cdecl以及_stdcall  回复  更多评论   

@Kenny Jiang
你可以仔细看看我的回复以及楼上,我觉得你理解的有问题
2011-04-20 13:33 | 溪流

# re: 函数调用的区别:_cdecl以及_stdcall  回复  更多评论   

@陈梓瀚(vczh)
恩,你说的对。
gcc能调用win api是因为win api缺省都是_stdcall,由被调者销毁堆栈。如果是cdecl那么就会出现问题。而C++/c缺省是cdecl,所以在导出函数接口时(如自己写的DLL),需要特别强调是_stdcall才可以被别的语言正常使用。
2011-04-20 17:11 | Kenny Jiang

# re: 函数调用的区别:_cdecl以及_stdcall  回复  更多评论   

@溪流
呵呵,压栈是主调函数做的,但是出栈就不同了。
一点都不给博主点面子,回复很犀利,呵呵。
2011-04-20 17:12 | Kenny Jiang

# re: 函数调用的区别:_cdecl以及_stdcall  回复  更多评论   

@Kenny Jiang
正因为压栈是主调函数做的,所以,如果存在你说的“编译器不同导致‘栈不同’”的问题,出栈工作不是应该同样由主调函数承担才不至于出问题吗?所以从这一点上说,__cdecl 显得更合理。
其他的,是否语言间能互相识别,只要遵循同样的调用约定就可以了,不存在某种调用约定的特殊优势。

2011-04-20 20:55 | 溪流

# re: 函数调用的区别:_cdecl以及_stdcall  回复  更多评论   

http://msdn.microsoft.com/en-us/library/zxk0tw93%28v=vs.71%29.aspx
2013-07-17 10:52 | paster

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理