zoukankan      html  css  js  c++  java
  • C/C++/动态链接库DLL中函数的调用约定与名称修饰

    参见:http://blog.twofei.com/cc/impl/calling-convension.html

        调用约定(Calling Convention)是指在程序设计语言中为了实现函数调用而建立的一种协议.
    这种协议规定了该语言的函数中的参数传送方式,参数是否可变和由谁来处理堆栈等问题. 不同的语
    言定义了不同的调用约定.

        在C++中,为了允许操作符重载和函数重载,C++编译器往往按照某种规则改写每一个入口点的符号名,
    以便允许同一个名字(具有不同的参数类型或者是不同的作用域)有多个用法,而不会打破现有的基于C的链
    接器. 这项技术通常被称为名称改编(Name Mangling)或者名称修饰(Name Decoration). 许多C++编译
    器厂商选择了自己的名称修饰方案.

        因此,为了使其它语言编写的模块(如Visual Basic应用程序,Pascal或Fortran的应用程序等)可以调
    用C/C++编写的函数,必须使用正确的调用约定来导出函数,并且不要让编译器对要导出的函数进行任何名称修饰.



    1.调用约定(Calling Convention)
        调用约定用来处理决定函数参数传送时入栈和出栈的顺序(由调用者还是被调用者把参数弹出栈),以及编译
    器用来识别函数名称的名称修饰约定等问题. 在Microsoft VC++ 6.0中定义了下面几种调用约定,我们将结合汇
    编语言来一一分析它们:

      *** C语言中的调用约定 ***
        1. __cdecl
            __cdecl是C/C++(非类成员函数以及静态类成员函数)程序默认使用的调用约定,也可以在函数声明时
        加上 __cdecl 关键字 来手工指定. 采用__cdecl约定时,函数参数按照 从右到左 顺序入栈,并且由调用
        函数者把参数弹出栈以清理堆栈. 因此,实现可变参数的函数只能使用该调用约定. 由于每一个使用
        __cdecl约定的函数都要包含清理堆栈   的代码,所以产生的可执行文件大小会稍大一丁点.

        下面将通过一个具体实例来分析__cdecl约定:

            int __cdecl Add(int a,int b)
            {
                return a+b;
            }
    
            void main(void)
            {
                Add(1,2);
            }

       函数调用处反汇编代码如下:

            main:
            0134474E 6A 02                push        2                 ;压入参数2(最右边)
            01344750 6A 01                push        1                 ;压入参数1(右边倒数第2个)
            01344752 E8 F5 CC FF FF       call        Add (0134144Ch)   ;调用目标函数
            01344757 83 C4 08             add         esp,8             ;恢复堆栈
            
            Add:
            00A8291E 8B 45 08             mov         eax,dword ptr [a] 
            00A82921 03 45 0C             add         eax,dword ptr [b]  
            00A8292A C3                   ret                           ;被调用者不清理堆栈

      由此可见,在__cdecl中:
            1.参数从右到左依次传递
            2.参数个数等于函数声明时的个数(变参除外)
            3.由调用者清理堆栈



      2. __stdcall
            __stdcall调用约定用于调用Win32 API函数 Win32API的WINAPI/CALLBACK等都是它. 采用__stdcall
        约定时,函数参数按照 从右到左 的顺序入栈,被调用的函数在返回前清理参数堆栈,函数参数个数固定.
        由于函数体本身知道传进来的参数个数,因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈.

        还是上面那个例子,将__cdecl约定换成__stdcall:

            int __stdcall Add(int a,int b)
            {
                return a+b;
            }
    
            void main(void)
            {
                Add(1,2);
            }

        函数调用处反汇编代码(从简):

            main:
            ;Add(1,2);
            0131474E 6A 02                push        2                 ;参数依然是从右到左传递
            01314750 6A 01                push        1  
            01314752 E8 FA CC FF FF       call        Add (01311451h) 
            01314757 33 C0                xor         eax,eax           ;调用函数并不清理堆栈
            0131476C C3                   ret
            
            int __stdcall Add(int a,int b):
            0131291E 8B 45 08             mov         eax,dword ptr [a] ;完成相加操作
            01312921 03 45 0C             add         eax,dword ptr [b]
            0131292A C2 08 00             ret         8                 ;返回时清理参数栈
            

          由此可见,在__stdcall中:
               1.函数参数从右到左传递
               2.参数个数等于函数声明时的个数
               3.由被调用者清理参数栈



        3. __fastcall
            __fastcall约定用于对性能要求非常高的场合.
            __fastcall约定将函数的从左边开始的两个大小不大于4个字节(DWORD)的参数分别放在ECX和EDX寄存器,
        其余的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的堆栈.
            (暂无代码)
            1.函数参数从右到左传递
            2.由被调用者清理堆栈



      *** C++中的调用约定 ***
            以上三种C语言中可用的调用约定均可以用于修饰C++中的非静态成员函数,只是会将this指针当作一个普通的
        参数一样压入(__cdecl,__stdcall)/传递到ECX(EDX)(__fastcall).
            __thiscall调用约定是C++中专用的,是非静态类成员函数的默认调用约定(注:在VC6中没有此关键字).
        采用 __thiscall约定时,函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,
        只是它是通过ECX寄存器传送一个额外的参数:this指针

        这次的例子中将定义一个类,并在类中定义一个成员函数,代码如下:

            class A
            {
            public:
                int __cdecl Add1(int a,int b)
                {
                    return a+b;
                }
    
                int __stdcall Add2(int a,int b)
                {
                    return a+b;
                }
    
                int __fastcall Add3(int a,int b)
                {
                    return a+b;
                }
    
                int __thiscall Add4(int a,int b)
                {
                    return a+b;
                }
            };
    
            void main(void)
            {
                A a;
                a.Add1(1,2);
                a.Add2(3,4);
                a.Add3(5,6);
                a.Add4(7,8);
            }


        再来看看这个稍微复杂一点的C++类调用的反汇编代码:
            1. 除 Add3 的__fastcall外其余全部通过从右到左参数
            2. 除默认的函数需要的参数个数外, 额外传入一个this指针
            3. 清理堆栈的方式保持不变

                a.Add1(1,2);
            001C1528 6A 02                push        2  
            001C152A 6A 01                push        1  
            001C152C 8D 45 F7             lea         eax,[a]  
            001C152F 50                   push        eax                   ;传入this指针
            001C1530 E8 1B FB FF FF       call        A::Add1 (01C1050h)  
            001C1535 83 C4 0C             add         esp,0Ch               ;由于多传入了一个this,所以是0Ch
            
                a.Add2(3,4);
            001C1538 6A 04                push        4  
            001C153A 6A 03                push        3  
            001C153C 8D 45 F7             lea         eax,[a]               ;传入this指针
            001C153F 50                   push        eax  
            001C1540 E8 DD FB FF FF       call        A::Add2 (01C1122h)  
            
                a.Add3(5,6);
            001C1545 6A 06                push        6  
            001C1547 BA 05 00 00 00       mov         edx,5                 ;将参数保存到edx
            001C154C 8D 4D F7             lea         ecx,[a]               ;将this保存到ecx
            001C154F E8 60 FB FF FF       call        A::Add3 (01C10B4h)  
            
                a.Add4(7,8);
            001C1554 6A 08                push        8  
            001C1556 6A 07                push        7  
            001C1558 8D 4D F7             lea         ecx,[a]               ;由ecx传递this指针
            001C155B E8 B4 FA FF FF       call        A::Add4 (01C1014h)  
            
            类成员反汇编代码(仅保留返回部分,其余地方同C语言中一样):
                __cdecl:    001C141A C3                   ret               ;__cdecl由调用者清理堆栈
                __stdcall:  001C145A C2 0C 00             ret         0Ch   ;加上this,总共3个参数
                __fastcall: 001C14A2 C2 04 00             ret         4     ;this和其中一个由寄存器传递
                __thiscall: 001C14EF C2 08 00             ret         8     ;this通过ecx传递,堆栈不受影响
        

    <名称修饰,后面补上>
    ----------------
    女孩不哭 @ 2013-09-11 00:18:11 @ http://www.cnblogs.com/nbsofer

  • 相关阅读:
    GitLab 介绍
    git 标签
    git 分支
    git 仓库 撤销提交 git reset and 查看本地历史操作 git reflog
    git 仓库 回退功能 git checkout
    python 并发编程 多进程 练习题
    git 命令 查看历史提交 git log
    git 命令 git diff 查看 Git 区域文件的具体改动
    POJ 2608
    POJ 2610
  • 原文地址:https://www.cnblogs.com/memset/p/calling_convention.html
Copyright © 2011-2022 走看看