• C++的类和new和delete和菱形继承机制


    参考

    https://showlinkroom.me/2017/08/21/C-%E9%80%86%E5%90%91%E5%88%86%E6%9E%90/
    https://www.cnblogs.com/bonelee/p/17299985.html
    https://xz.aliyun.com/t/5242?time__1311=n4%2BxnD07itGQ%3DD54AKDsA3xCuU%3DwQNY4D&alichlgref=https%3A%2F%2Fxz.aliyun.com%2Fu%2F15779#toc-3

    虚函数

    在C++中,虚函数(virtual function)是一种特殊的成员函数,它允许在派生类中重写或覆盖基类中定义的函数,从而实现多态性(polymorphism)。虚函数的关键特征和用途可以概括如下:

    1. 声明与定义
      要声明一个虚函数,需要在基类的成员函数声明前加上virtual关键字。例如:

      class Base {
      public:
          virtual void someFunction() {
              // 基类的实现
          }
      };
      

      派生类可以重写该函数,也可以不重写,如果重写,则在派生类中提供新的实现:

      class Derived : public Base {
      public:
          void someFunction() override {
              // 派生类的实现
          }
      };
      
    2. 虚函数表(VTable)
      实现虚函数的机制通常涉及到虚函数表(Virtual Table,简称vtable)。每个含有虚函数的类都会有一个虚函数表,表中存储着该类所有虚函数的地址。每个对象都有一个指向相应类虚函数表的指针(称为vptr)。当通过基类指针或引用调用虚函数时,程序会根据对象的vptr查找到正确的虚函数表,从而调用实际对象类型的函数实现。

    3. 纯虚函数与抽象类
      如果基类中的虚函数没有提供实现,并且在声明时被= 0初始化,那么这个函数被称为纯虚函数。含有纯虚函数的类不能实例化,只能作为基类被继承,这样的类称为抽象类。

    使用虚函数的class结构

    在这里插入图片描述

    相关实现

    在这里插入图片描述

    • new的本质,实际上就是malloc+构造函数
    • delete的本质就是析构函数+free
      在这里插入图片描述

    源码

    #include 
    #include 
    
    using namespace std;
    
    class Person
    {
    
    public:
        Person() {
            age = 0;
            name = "";
        };
        ~Person() {
    
        };
        virtual void setName(string name) = 0;
        virtual void setAge(int age) = 0;
    protected:
        int age;
        string name;
    
    };
    
    class Student :public Person {
    
    public:
        Student() {
            age = 17;
        }
        void setName(string name) {
            this->name = name;
        }
        string getName() {
            return this->name;
        }
        void setAge(int age) {
            if (age > 30 || age < 6) {
                cout << "not suitable for school!" << endl;
                return;
            }
            this->age = age;
        }
        int getAge() {
            return this->age;
        }
    };
    
    int main() {
        Student* stu = new Student();
        stu->setName("Link");
        stu->setAge(18);
        cout << stu->getName() << " with age " << stu->getAge() << endl;
        return 0;
    }
    

    IDA反编译

    在这里插入图片描述
    子类构造函数
    在这里插入图片描述
    父类构造函数
    在这里插入图片描述

    子类虚表和父类虚表

    在这里插入图片描述
    纯虚函数的抽象类的虚表由__cxa_pure_virtual填充,有几个纯虚函数就有几个__cxa_pure_virtual,student类虚表分别有各自实现的虚函数,虚表上面是该类的typeinfo的地址
    在这里插入图片描述

    • RTTI (Run-Time Type Information) 是C++的一项特性,允许程序在运行时识别对象的类型。
    • typeid运算符和dynamic_cast都是基于RTTI实现的。
    • 每个包含虚函数的类实例,在内存中都有一个隐藏的指针(称为vptr,虚函数指针),指向该类的虚函数表(vtable)。这个vptr是在对象创建时由编译器自动初始化的。
    • RTTI信息通常与vtable一起管理,其中可能包括类型描述信息和继承链信息等。
    1. Aclass* ptra = new Bclass;:这行代码创建了一个指向Bclass实例的指针,但这个指针被声明为Aclass*类型。因为BclassAclass的子类(假设如此),所以这是向上转型,是多态的基础。

    2. int ** ptrvf = (int**)(ptra);:这里进行了一次不安全的类型转换,将Aclass指针转换为了int**。这种转换通常是为了直接访问虚函数指针(vptr),因为在许多系统上,vptr是对象内存布局的第一个元素,可以被视为一个指针的指针。但是,请注意,这种做法非常危险且非标准,违反了类型安全原则。

    3. *((int*)ptrvf[0]-1):这一部分是为了找到RTTI信息的位置。ptrvf[0]访问的是第一个虚函数(按指针算起),减1则是尝试访问vptr前的一个位置,期望那里存放着RTTI相关数据的指针。这一步同样非常危险,依赖于特定编译器和平台的实现细节。

    4. *((RTTICompleteObjectLocator*)(...)):这里进一步将找到的地址强制转换为RTTICompleteObjectLocator指针,并解引用它。RTTICompleteObjectLocator是一个内部使用的结构(非标准公开接口),用于存储有关对象类型的完整信息,比如类的类型描述符、偏移量到类型名称字符串等。

    调用函数

    在这里插入图片描述
    对于虚函数

    1. 先通过对象开始八个字节得到虚表地址
    2. 通过虚表地址和相关偏移得到函数地址
    3. 调用函数
      对于非虚函数
    4. 直接调用再text段实现的函数

    菱形继承

    #include
    #include
    
    //间接基类
    class A {
    public:
        virtual void function() {
            printf("A virtual function\n");
        }
        int a;
    };
    
    //直接基类
    class B :virtual public A { //虚继承
    public:
        virtual void func() {
            printf("B virtual func()\n");
        }
        int b;
    };
    
    //直接基类
    class C :virtual public A { //虚继承
    public:
        virtual void func() {
            printf("C virtual func()");
        }
        int c;
    };
    
    //派生类
    class D :public B, public C {
    public:
        virtual void function() {
            printf("D virtual function()");
        }
        int d;
    };
    
    
    int main(int argc, char** argv) {
        A* A_ptr = (A*)new D();
        A_ptr->function();
    
        return 0;
    }
    

    B和C继承A,D继承B和C

    在这里插入图片描述通过虚表地址减去24后的位置的内容是32,然后再加上起始地址,对应到的V4为D内的A的对象内存位置,offset vbase应该是与虚基类的偏移也就是和A的偏移
    在这里插入图片描述
    在这里插入图片描述
    这里给各个父类对象在本对象的空间的前八个字节赋值时,用的是本对象虚表对应到的不同父类的虚函数的地址
    在这里插入图片描述

    此时对应的虚表地址,所以**两次得到D::function的地址,然后参数为对象自己
    在这里插入图片描述

  • 相关阅读:
    Java性能优化-书写高质量SQL的建议(如何做Mysql优化)
    openAI宫斗感想 chatGPT带给客户巨大利益的就是王者。王者终究会归来。技术人员不要总是想掌握所有核心技术并用到极致。
    基于Android的旅游管理系统 微信小程序
    Android 架构思想与 MVVM 框架封装
    git diff,stash,submodule,format-patch
    从一到无穷大 #13 How does Lindorm TSDB solve the high cardinality problem?
    docker通过挂载conf文件启动redis
    roarctf_2019_easy_pwn
    InFusion:通过从扩散先验学习深度补全来进行图像 3D 高斯修复
    Spring Data的Repositories----Query by Example
  • 原文地址:https://blog.csdn.net/llovewuzhengzi/article/details/136693224