函数调用规范__cdecl和__stdcall的区别一目了然 (表格形式)

Posted on Tuesday, May 24, 2005 3:23 PM #C & C++

 

__cdecl

 

 

__stdcall

 

C C++ 程序的缺省调用规范

 

为了使用这种调用规范,需要你明确的加上 __stdcall (或 WINAPI )文字。即 return-type __stdcall function-name [(argument-list )]

 

 

调用函数 (Callee) 返回 ,由调用方 (Caller) 调整堆栈。

 

1. 调用方的函数调用

                                         

2. 被调用函数的执行

 

3. 被调用函数的结果返回

 

4. 调用方清除调整堆栈

                                                                

 

调用函数 (Callee) 返回 ,由 调用函数 (Callee) 调整堆栈。图示:

 

1. 调用方的函数调用

 

2. 被调用函数的执行

 

3. 被调用函数清除调整堆栈

 

4. 被调用函数的结果返回                                                           

 

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

 

 

因为调整堆栈的代码只存在在一个地方(被调用函数的代码内),所以最后生成的文件较小。

 

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

 

 

函数的参数个数不能是可变的。

 

对于定义在 C 程序文件中的输出函数,函数名会保持原样,不会被修饰。

对于定义在 C++ 程序文件中的输出函数,函数名会被修饰, MSDN Underscore character (_) is prefixed to names . 我实际测试( VC4 VC6 )下来发现好像不是那么简单。

可通过在前面加上 extern C 以去除函数名修饰。也可通过 .def 文件去除函数名修饰。

 

不论是 C 程序文件中的输出函数还是 C++ 程序文件中的输出函数,函数名都会被修饰。

对于定义在 C 程序文件中的输出函数, An underscore (_) is prefixed to the name. The name is followed by the at sign (@) followed by the number of bytes (in decimal) in the argument list.

对于定义在 C++ 程序文件中的输出函数,好像更复杂,和 __cdecl 的情况类似。

好像只能通过 .def 文件去除函数名修饰。

 

 

_beginthread 需要 __cdecl 的线程函数地址

 

 

_beginthreadex CreateThread 需要 __stdcall 的线程函数地址

 

 

两者的参数传递顺序都是从右向左。

为了让 VB 可以调用,需要用 __stdcall 调用规范来定义 C/C++ 函数。请参看Microsoft KB153586 文章:How To Call C Functions That Use the _cdecl Calling Convention

当你 LoadLibrary 一个 DLL 文件后, 把 GetProcAddress 取得的函数地址传给上面三个线程生成函数时,请务必确认实际定义在 DLL 文件的输出函数符合调用规范要求。否则,编译成 Release 版后运行,可能会破坏堆栈,程序行为不可预测。

VC 中的相关编译开关:/Gd /Gr /Gz 。另外,VC6 中新增加的 /GZ 编译开关可以帮你检查堆栈问题。

我也是初学者,若有不对的地方、可以补充的地方,请指教。谢谢。  

 

(补充)汇编语言视点的比较文章: Intel x86 Function-call Conventions - Assembly View

 

 

调用约定    
   
  调用约定(Calling   convention)决定以下内容:函数参数的压栈顺序,由调用者还是被调用者把参数弹出栈,以及产生函数修饰名的方法。MFC支持以下调用约定:    
   
   
  _cdecl    
   
  按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于“C”函数或者变量,修饰名是在函数名前加下划线。对于“C++”函数,有所不同。    
   
  如函数void   test(void)的修饰名是_test;对于不属于一个类的“C++”全局函数,修饰名是?test@@ZAXXZ。    
   
  这是MFC缺省调用约定。由于是调用者负责把参数弹出栈,所以可以给函数定义个数不定的参数,如printf函数。    
   
   
  _stdcall    
   
  按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。对于“C”函数或者变量,修饰名以下划线为前缀,然后是函数名,然后是符号“@”及参数的字节数, 如函数int   func(int   a,   double   b)的修饰名是_func@12。对于“C++”函数,则有所不同。    
   
  所有的Win32   API函数都遵循该约定。    
   
   
  _fastcall    
   
  头两个DWORD类型或者占更少字节的参数被放入ECX和EDX寄存器,其他剩下的参数按从右到左的顺序压入栈。由被调用者把参数弹出栈,对于“C”函数 或者变量,修饰名以“@”为前缀,然后是函数名,接着是符号“@”及参数的字节数,如函数int   func(int   a,   double   b)的修饰名是@func@12。对于“C++”函数,有所不同。    
   
  未来的编译器可能使用不同的寄存器来存放参数。    
   
   
  thiscall    
   
  仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右到左压栈。thiscall不是关键词,因此不能被程序员指定。    
   
   
  naked   call    
   
  采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked   call不产生这样的代码。    
   
  naked   call不是类型修饰符,故必须和_declspec共同使用,如下:    
   
  __declspec(   naked   )   int   func(   formal_parameters   )    
   
  {    
   
  //   Function   body    
   
  }    
   
   
  过时的调用约定    
   
  原来的一些调用约定可以不再使用。它们被定义成调用约定_stdcall或者_cdecl。例如:    
   
  #define   CALLBACK   __stdcall    
   
  #define   WINAPI   __stdcall    
   
  #define   WINAPIV   __cdecl    
   
  #define   APIENTRY   WINAPI    
   
  #define   APIPRIVATE   __stdcall    
   
  #define   PASCAL   __stdca

阅读更多
个人分类: C/C++系列
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭