• 调用约定


    // Class.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include <iostream>
    #include <Windows.h>
    using namespace std;
    class Base          //Derive的间接基类
    {
    public:
        virtual void func1()
        {
            cout << "Base::func1()" << endl;
        }
        virtual void func2()
        {
            cout << "Base::func2()" << endl;
        }
        int b;
    };
    class Base1 :public Base  //Derive的直接基类
    {
    public:
        virtual void func1()          //重写Base的func1()
        {
            cout << "Base1::func1()" << endl;
        }
        virtual void func3()
        {
            cout << "Base1::func3()" << endl;
        }
    private:
        int b1;
    };
    class Base2 :public Base    //Derive的直接基类
    {
    public:
        virtual void func1()       //重写Base的func1()
        {
            cout << "Base2::func2()" << endl;
        }
        virtual void func4()
        {
            cout << "Base2::func4()" << endl;
        }
    private:
        int b2;
    };
    class Derive :public Base1, public Base2
    {
    public:
        virtual void func1()          //重写Base1的func1()
        {
            cout << "Derive::func1()" << endl;
        }
        virtual void func5()
        {
            cout << "Derive::func5()" << endl;
        }
    private:
        int d;
    };
    typedef struct _DATA
    {
        int a;
        //int &b = a;
    }DATA;
    //MAX_PATH
    
    
    int __cdecl Test(int a, int b, int c, int d, int e)
    {
        return a + b + c + d + e;
    }
    int __stdcall Test_Stdcall(int a, int b, int c, int d, int e)
    {
        return a + b + c + d + e;
    }
    int __fastcall Test_Fastcall(int a, int b, int c, int d, int e)
    {
        return a + b + c + d + e;
    }
    
    int main()
    {
        Test(1, 2, 3, 4, 5);
        /*
        0082176E 6A 05                push        5  
        00821770 6A 04                push        4  
        00821772 6A 03                push        3  
        00821774 6A 02                push        2  
        00821776 6A 01                push        1  
        00821778 E8 BD FA FF FF       call        Test (082123Ah)  
        0082177D 83 C4 14             add         esp,14h  
        */
        Test_Stdcall(1, 2, 3, 4, 5);
        /*
        00991DB0 6A 05                push        5  
        00991DB2 6A 04                push        4  
        00991DB4 6A 03                push        3  
        00991DB6 6A 02                push        2  
        00991DB8 6A 01                push        1  
        00991DBA E8 8E F5 FF FF       call        Test_Stdcall (099134Dh)  
        */
        Test_Fastcall(1, 2, 3, 4, 5);    //edx ecx
        /*
        0132264F 6A 05                push        5  
        01322651 6A 04                push        4  
        01322653 6A 03                push        3  
        01322655 BA 02 00 00 00       mov         edx,2            
        0132265A B9 01 00 00 00       mov         ecx,1  
        0132265F E8 EE EC FF FF       call        Test_Fastcall (01321352h) 
        */
         /*
         原来64位平台下只有一种变形的__fastcall的调用约定,
         前4参数则先放入ecx、edx、r8、r9寄存器,更多的参数放入栈区。
         这个时候我们要注意的是,
         在64位下,系统还是为前4个参数预留了栈区空间(每个栈空间大小为8字节,共32字节大小),
         然后将基存器的值放入所预留的栈区空间。
         为什么系统要多此一举呢?我们都知道寄存器传递参数速度要远大于栈区传值,
         而将寄存器中的值再放入栈区预留空间,这是为了防止在传递参数的过程中,
         寄存器需要接收其他的值而导致参数无法传递,或者其他值无法接收的情况。
         */
        return 0;
    }
    View Code

    首先让我们看看一个函数调用到底需要经历哪几个过程,编译器到底为我们做了些什么。
    1. 把函数的参数压栈或者储存到寄存器
    2. 跳转到函数
    3. 把函数使用到的一些寄存器压栈
    4. 执行函数
    5. 处理函数返回值
    6. 对于第3步中压栈的那些寄存器,恢复它们原来的值
    7. 根据不同的调用约定,清除第1步中压栈的参数,然后返回,或者先返回然后清除。


    调用约定主要是定义了第1,7步骤的规则:
    怎么去传递参数,谁负责去清除栈上的参数;


    在跨(开发)平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用_cdecl。到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用__stdcall关键字。

    1. __cdecl
    __cdecl是由调用者来清除栈上的参数。

    2. __stdcall
    WINAPI, CALLBACK实际上都是定义成__stdcall。
    通过比较,我们可以立刻发现__stdcall和__cdecl的反汇编有两个不同点:
    a. __stdcall函数返回的时候使用了“ret 10h”,而__cdecl使用的是“ret”,这表明__stdcall函数在返回的时候就清除了4个参数(大小为10h),这个是函数实现部分来做的,而不是由调用者来做
    b. 正因为函数本身已经清除了栈上的参数,调用者不需要在"call f2"之后再使用“add esp,10h”了。
    可以看到__stdcall把函数返回和清除栈上函数合二为一,用一句“ret xxx”搞定,比__cdecl方便很多,那为什么不全部使用__stdcall呢?

    这是因为 __stdcall有一个不足之处:它不能使用于那些可变参数个数的函数,比如printf, sprintf没有办法使用__stdcall。因为函数本身不知道每次调用时到底有几个参数,所以它无法确定ret后面的数字,这项工作只能让调用者自己去做。因此类似于printf, sprintf的函数都是使用__cdecl。注意:在VS2005中,如果给可变参数个数的函数用了__stdcall关键字,编译器不会报错,但是它实际上还是按照__cdecl调用约定进行编译,通过查看反汇编,然后和前面列出的反汇编进行比较,就会发现它用的是__cdecl。

    3.__fastcall
    从上面最后一行反汇编"ret 8"可以看到,__fastcall和__stdcall一样,也是函数本身来清除栈上的参数,这也就意味着__fastcall也有__stdcall的缺点:不支持可变参数个数的函数。
    和__stdcall不同的是,__fastcall把第一,第二个参数放到了寄存器中,而不是压栈,因为寄存器的读写速度比栈快很多,这也就是为什么它叫快速调用(fastcall。注意:在VC中的某些情况下,__fastcall比__stdcall和__cdecl慢)。
    再介绍其他调用约定之前,让我们先回顾一下__cdecl,__stdcall和__fastcall。并且补充一些它们之前的区别(这些区别不太重要,所以上面没有讨论,只在这里列出)

      __cdecl __stdcall __fastcall
     压栈顺序 从右到左 从右到左 从右到左,前两个参数放在ecx, edx
    谁清除栈上参数 调用者(caller) 函数(被调用者callee) 函数(被调用者callee)
    默认调用约定的编译器参数 /Gd /Gz /Gr
    可变参数个数的函数 支持 不支持 不支持
    C的函数名修饰规范Name-decoration convention 加下划线前缀,如:_func 下划线开头,函数名,然后@符号,最后是参数的总byte数。如:int f(int a, double b ),名字为_f@12 以@开头,其他和__stdcall一样。如:@f@12

    __thiscall(过时)
    这是 C++ 语言特有的一种调用方式,用于类成员函数的调用约定。如果参数确定,this 指针存放于 ECX 寄存器,函数自身清理堆栈;
    如果参数不确定,this指针在所有参数入栈后再入栈,调用者清理栈。__thiscall 不是关键字,程序员不能使用。参数按照从右至左的方式入栈

    爱程序 不爱bug 爱生活 不爱黑眼圈 我和你们一样 我和你们不一样 我不是凡客 我要做geek
  • 相关阅读:
    Kindeditor 代码审计
    tamper参数
    大学站注入点(安全狗)
    sqlmap注入小结
    tamper绕WAF详解
    网站安全狗最新版绕过测试
    大学站防SQL注入代码(ASP版)
    防SQL注入代码(ASP版)
    xss利用和检测平台
    tamper绕WAF小结
  • 原文地址:https://www.cnblogs.com/yifi/p/6480570.html
Copyright © 2020-2023  润新知