• C/C++ __cdecl和__stdcall


    一、先介绍几个背景知识

    1. C/C++程序中函数参数入栈顺序默认是从右至左的。 这么设计是为了支持参数个数动态变化。先从栈中取出的,肯定是最左边的参数,这样就能够支持最右边的参数是可选的。反过来想想,如果采用自左向右的入栈方式,最前面的参数被压在栈底,这种情况下只有事先确定了参数个数,才能通过栈指针的相对位移求得最左边的参数,所以就无法支持参数个数动态变化了。

    2. C/C++程序,栈是从高地址向地地址生长的,也即栈底为高地址,栈顶为低地址。结合1和2,其实我们就可以自己写个C++小程序来验证入栈顺序了。默认情况下, 右边的参数先入栈,就是高地址,左边的参数后入栈,就是低地址。

    3. C/C++程序,函数调用结束后,可以由调用者负责清空栈,也可以由函数自身负责情况栈。调用者负责清空堆栈的话,因为每个调用的地方都需要生成一段调整堆栈的代码,所以最后生成的文件较大。

     

    二、__cdecl和__stdcall的区别

    __cdecl和__stdcall的区别如下表:

    调用约定 入栈顺序 函数调用结束后谁负责清空堆栈 函数名修饰 说明
    __cdecl 从右到左 调用者 见文中描述 C和C++程序的缺省调用方式
    __stdcall 从右到左 函数自身 见文中描述  

    函数名修饰

    1. C语言函数名修饰:

    __cdecl:在函数的前面加上下划线前缀;如 double add(double a, double b) 被修饰为 _add。

    __stdcall:函数名前加上一个下划线前缀,函数名后面加上一个“@”符号和其参数的字节数;如 double add(double a, double b) 被修饰为_add@16。

    2. C++ 语言函数名修饰

    C++的函数名修饰规则有些复杂,但是信息更充分,通过分析修饰名不仅能够知道函数的调用方式,返回值类型,参数个数甚至参数类型。不管__cdecl,还是__stdcall调用方式,函数修饰都是以一个“?”开始,后面紧跟函数的名字,后面再跟返回类型,再后面是参数表的开始标识和按照参数类型代号拼出的参数表,参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。如 double add(double a, double b) 被修饰为 ?add@@YANNN@Z 。

    X--void
    D--char
    E--unsigned char
    F--short
    H--int
    I--unsigned int
    J--long
    K--unsigned long(DWORD)
    M--float
    N--double
    _N--bool
    U--struct

    对于__stdcall方式,参数表的开始标识是“@@YG”,对于__cdecl方式则是“@@YA”。

    还有当参数列表有指针的时候,指针的方式有些特别,用PA表示指针,用PB表示const类型的指针。后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复。

    举一个简单的例子: int fun(int *p1, int *p2); 会被修饰为(?fun@@YAHPAH0@Z)。

    还有在C++中的成员函数中公有和私有的成员函数的修饰也有相应的表示符。总而言之,在C++环境中的函数名修饰的时候,会带有参数列表的信息,还有返回值的信息,所以在C++中的函数重载就是允许存在的,因为它可以根据你的参数列表选择对应的函数,而显然在我们的C环境下是不允许的。

    三、如何选择使用__cdecl和__stdcall

    何时使用stdcall?

      _stdcall主要用于windows API 。如果我们的函数使用了_cdecl,那么栈的清除工作是由调用者,用 COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨(开发)平台的调用中,我们都使用__stdcall(虽然有时是以 WINAPI的样子出现)。

    何时使用cdecl?

      _cdecl对于变长参数适用。当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知 道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用_cdecl。

     

    四、extern "C"

    前面讲到了不论是cdecl还是stdcall,C和C++对函数名修饰都是不同的规则。那么在C和C++之前互相引用的时候,就要使用extern C了。

    情况1::在C++中包含C语言写的头文件的时候,将C语言头文件包含在extern "C" 中,这样C++才能调用C声明和定义的函数。如下是一个常见的代码结构,

     1 #ifndef __INCvxWorksh /*防止该头文件被重复引用*/
     2 #define __INCvxWorksh
     3 #ifdef __cplusplus        
     4 extern "C"{           //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
     5 #endif
     6  
     7 /**/
     8  
     9 #ifdef __cplusplus
    10 }
    11  
    12 #endif
    13 #endif /*end of __INCvxWorksh*/

     情况2:C中引用C++语言中的函数或者变量时,C++的头文件需要加上extern “C”,但是C语言中不能直接引用声明了extern “C”的该头文件,应该仅在C中将C++中定义的extern “C”函数声明为extern类型。

     1   /* c++头文件cppExample.h */ 
     2   #ifndef CPP_EXAMPLE_H 
     3   #define CPP_EXAMPLE_H 
     4   extern "C" int add(int x, int y); 
     5   #endif
     6  
     7   /* c实现文件cFile.c */ 
     8   extern int add(int x, int y); 
     9   int main() 
    10   { 
    11     add(2, 3); 
    12     return 0; 
    13   }
  • 相关阅读:
    Redis 查看、删除keys
    gitlab 备份和恢复
    gitlab的搭建
    certbot 域名续期及证书查看
    晴天(周杰伦)
    SSH Permission denied (publickey,gssapi-keyex,gssapi-with-mic)
    jenkins miaration section 1
    jenkins 忘记管理员密码
    Yangk's-树状数组 模板
    codeforces-977F-Consecutive Subsequence【动态规划】
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/16731301.html
Copyright © 2020-2023  润新知