zoukankan      html  css  js  c++  java
  • 【C/C++开发】__stdcall,__cdecl,__fastcall的区别

    __stdcall和__cdecl的区别

    __stdcall和__cdecl是两种函数名字修饰。(注意是连续的两个下划线)

    Windows上

    windows上不管是C还是C++,默认使用的都是__stdcall方式。

    不论__stdcall还是__cdecl函数参数都是从可向左入栈的,并且由调用者完成入栈操作。对于__stdcall方式被调用者自身在函数返回前清空堆栈;而__cdecl则由调用者维护内存堆栈,所以调用者函数生成的汇编代码比前一种方式长。

    由__cdecl约定的函数只能被C/C++调用。

    Windows上使用dumpbin工具查看函数名字修饰。

    C语言

    __stdcall方式:_FuncName@sizeofParameters

    例如:int __stdcall test(int a,double b)编译之后完整的函数名为_test@12

    __cdecl方式:_FuncName

    例如:int __stdcall test(int a,double b)编译之后完整的函数名为_test

    由于C++允许重载函数,所以函数的名字修饰就不能像C这么简单,C++中的函数名字修饰应该包含返回类型,各参数类型等信息,如果是类成员函数,还应该包含类名、访问级别、是否为const函数等等信息。

    C++语言

    不管__cdecl,__fastcall还是__stdcall调用方式,函数修饰都是以一个“?”开始,后面紧跟函数的名字,再后面是参数表的开始标识和按照参数类型代号拼出的参数表。对于__stdcall方式,参数表的开始标识是“@@YG”,对于__cdecl方式则是“@@YA”。

    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
    PA--指针

    PB--const类型的指针

    如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复;

    U表示结构类型,通常后跟结构体的类型名,用“@@”表示结构类型名的结束;

    函数参数表的第一项实际上是表示函数的返回值类型;

    参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。

    举例:

    int Function1 (char *var1,unsigned long);其函数修饰名为“?Function1@@YGHPADK@Z”
    void Function2();其函数修饰名则为“?Function2@@YGXXZ” 

    C++中调用由C编译器生成的函数

    extern "C" {

    long func(int a);

    char* strcat(char*,const char*);

    }

    extern "C" {

    #include<string.h>

    }

    要想在编译阶段就知道使用的编译器类型,可以使用:

    #ifdef __cplusplus
    cout<<"C++";
    #else
    printf("C");
    #endif

    通常应该这样声明头文件:

    #ifdef _cplusplus
    extern "C" {
    #endif
     
    long MakeFun(long lFun);
     
    #ifdef _cplusplus
    }
    #endif

    Linux上

    Linux上使用__stdcall和__cdecl的方式比较麻烦一些。

    int __attribute__((cdecl)) test();

    Linux上使用nm工具查看函数名字修饰。

    __stdcall和__cdecl没有区别,有区别的是编程语言。

    C++语言

    char test();  -----    _Z4testv            _Z表示C++,4代表函数名有4个字节,test是函数名,v代表参数为空

    double func(unsigned int a,double *b,char c);   -----      _Z4funcjPdc     j代表int,Pd代表double型指针,c代表char

    C语言

    只是简单一个函数名,没有其他修饰信息。

    char test();  -----    test

    double func(unsigned int a,double *b,char c);   -----      func

     

    附:

    Linux上的反汇编工具:objdump  -x  exefile

    查看二进制文件:hexdump -C biFile

    编辑二进制文件:hexedit biFile

    原文来自:博客园(华夏35度)http://www.cnblogs.com/zhangchaoyang 作者:Orisun


    调用约定:
    __cdecl __fastcall
     __stdcall,三者都是调用约定(Calling convention),它决定以下内容:1)函数参数的压栈顺序,2)由调用者还是被调用者把参数弹出栈,3)以及产生函数修饰名的方法。

    1
    __stdcall调用约定:函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,

    2
    _cdeclCC++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。注意:对于可变参数的成员函数,始终使用__cdecl的转换方式。

    3
    __fastcall调用约定:它是通过寄存器来传送参数的(实际上,它用ECXEDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈)。

    4
    thiscall仅仅应用于"C++"成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。

    5
     naked call采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESIEDIEBXEBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。

    调用约定可以通过工程设置:Setting..."C/C++ "Code Generation项进行选择,缺省状态为__cdecl

    名字修饰约定:

    1
    、修饰名(Decoration name)"C"或者"C++"函数在内部(编译和链接)通过修饰名识别
    2
    C编译时函数名修饰约定规则:
    __stdcall
    调用约定在输出函数名前加上一个下划线前缀,后面加上一个"@"符号和其参数的字节数,格式为_name@number,例如 (int a, int b),其修饰名为:_@8
    __cdecl
    调用约定仅在输出函数名前加上一个下划线前缀,格式为_name

    __fastcall
    调用约定在输出函数名前加上一个"@"符号,后面也是一个"@"符号和其参数的字节数,格式为@name@number

    3
    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"标识结束。
    其格式为"?name@@YG*****@Z""?name@@YG*XZ",例如
    int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z”
    void Test2() -----“?Test2@@YGXXZ”

    __cdecl
    调用约定:
    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"
    __fastcall
    调用约定:
    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YI"
    VC++
    对函数的省缺声明是"__cedcl",将只能被C/C++调用.

    注意:

    1
    _beginthread需要__cdecl的线程函数地址,_beginthreadexCreateThread需要__stdcall的线程函数地址。

    2
    、一般WIN32的函数都是__stdcall。而且在Windef.h中有如下的定义:
    #define CALLBACK __stdcall
    #define WINAPI
     __stdcall

    3
    extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);
    typedef int (__cdecl*FunPointer)(int a, int b);
    修饰符的书写顺序如上。


    4
     extern "C"的作用:如果Add(int a, int b)是在c语言编译器编译,而在c++文件使用,则需要在c++文件中声明:extern "C" Add(int a, int b),因为c编译器和c++编译器对函数名的解释不一样(c++编译器解释函数名的时候要考虑函数参数,这样是了方便函数重载,而在c语言中不存在函数重载的问题),使用extern "C",实质就是告诉c++编译器,该函数是c库里面的函数。如果不使用extern "C"则会出现链接错误。
    一般象如下使用:
    #ifdef _cplusplus
    #define EXTERN_C extern "C"
    #else
    #define EXTERN_C extern
    #endif

    #ifdef _cplusplus
    extern "C"{
    #endif
    EXTERN_C int func(int a, int b);
    #ifdef _cplusplus
    }
    #endif

    5
    MFC提供了一些宏,可以使用AFX_EXT_CLASS来代替__declspec(DLLexport),并修饰类名,从而导出类,AFX_API_EXPORT来修饰函数,AFX_DATA_EXPORT来修饰变量
    AFX_CLASS_IMPORT
    __declspec(DLLexport)
    AFX_API_IMPORT
    __declspec(DLLexport)
    AFX_DATA_IMPORT
    __declspec(DLLexport)
    AFX_CLASS_EXPORT
    __declspec(DLLexport)
    AFX_API_EXPORT
    __declspec(DLLexport)
    AFX_DATA_EXPORT
    __declspec(DLLexport)
    AFX_EXT_CLASS
    #ifdef _AFXEXT
    AFX_CLASS_EXPORT
    #else
    AFX_CLASS_IMPORT

    6
     DLLMain负责初始化(Initialization)和结束(Termination)工作,每当一个新的进程或者该进程的新的线程访问DLL时,或者访问DLL的每一个进程或者线程不再使用DLL或者结束时,都会调用DLLMain。但是,使用TerminateProcess TerminateThread结束进程或者线程,不会调用DLLMain


    7
    、一个DLL在内存中只有一个实例
    DLL
    程序和调用其输出函数的程序的关系:
    1)
    DLL与进程、线程之间的关系
    DLL
    模块被映射到调用它的进程的虚拟地址空间。
    DLL
    使用的内存从调用进程的虚拟地址空间分配,只能被该进程的线程所访问。
    DLL
    的句柄可以被调用进程使用;调用进程的句柄可以被DLL使用。
    DLLDLL
    可以有自己的数据段,但没有自己的堆栈,使用调用进程的栈,与调用它的应用程序相同的堆栈模式。

    2)
    、关于共享数据段
    DLL 
    定义的全局变量可以被调用进程访问;DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量,则需要使用同步机制;对一个DLL的变量,如果希望每个使用DLL的线程都有自己的值,则应该使用线程局部存储(TLSThread Local Strorage)

     

     

    -------------------------------------------------------------- 
    函数参数入栈的方式 
    --------------------------------------------------------------- 

    在函数调用过程中,会使用堆栈,这三个表示不同的堆栈调用方式和释放方式。 
    比如说__cdecl,它是标准的c方法的堆栈调用方式,就是在函数调用时的参数压入堆栈是与函数的声明顺序相反的,其它两个可以看MSDN,不过这个对我们编程没有太大的作用 
    --------------------------------------------------------------- 

    调用约定 

    调用约定(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 __stdcall



  • 相关阅读:
    POJ2778 DNA Sequence AC自动机上dp
    codeforces732F Tourist Reform 边双联通分量
    codeforces786B Legacy 线段树优化建图
    洛谷P3588 PUS 线段树优化建图
    codeforces1301D Time to Run 模拟
    codeforces1303B National Project 二分或直接计算
    codeforces1303C Perfect Keyboard 模拟或判断欧拉路
    codeforces1303D Fill The Bag 二进制应用+贪心
    python之路——使用python操作mysql数据库
    python之路——mysql索引原理
  • 原文地址:https://www.cnblogs.com/huty/p/8517776.html
Copyright © 2011-2022 走看看