zoukankan      html  css  js  c++  java
  • 类的内存结构

    cpp 类的内存结构


    说明:

    1. 虚表指针总是存在在类的头部,并按类的继承顺序排放。一个子类可以有多个虚表指针,且虚指针个数和具有虚函数的基类个数相同。

    2. 虚成员函数总是按照声明顺序存在于虚表中。

    3. 如果存在同名函数,子类虚函数会覆盖每一个父类的每一个同名虚函数。

    4. 子类独有的虚函数填入第一个虚函数表中,且用父类指针是不能调用。

    5. 父类独有的虚函数不会被覆盖覆盖。仅子类和该父类指针能调用。

    如下图类的内存结构图

    图中代码参考链接

    无虚函数

    class Drive
    {
    public:
        // virtual void vf() {}
        void f() {}
    };
    
    int main()
    {
        Drive d;
    
        cout << sizeof(d) << endl;
        return 0;
    }
    

    如下,类中没有虚函数,只有一个成员函数,以及其他默认构造析构函数。类似于空类,类的大小为1(注意空类大小不为0,因为为0的话,实例化后没法区分)。因此可以的出结论类的非虚成员函数信息不存在于对象实例中!

    Reading symbols from ./a.out...
    (gdb) b 17
    Breakpoint 1 at 0x1197: file demo.cpp, line 17.
    (gdb) r
    Starting program: /home/lester/Wdir/test/a.out 
    1
    
    Breakpoint 1, main () at demo.cpp:17
    17          return 0;
    (gdb) p d
    $1 = {<No data fields>}
    (gdb) $1 = {<No data fields>}
    (gdb) q
    A debugging session is active.
    
            Inferior 1 [process 54798] will be killed.
    
    Quit anyway? (y or n) y
    ~/Wdir/test >>> ./a.out                                                                                                                                                                      
    1
    ~/Wdir/test >>>      
    

    无继承

    代码:

    class Drive
    {
    public:
        virtual void vf() {}
        void f() {}
    };
    
    int main()
    {
        Drive d;
    
        return 0;
    }
    

    如下,子类经过强制类型转换,得到虚表指针,并提取虚表指针的内容,经过转换可以得到第一个虚函数。虚表中只有一个虚函数。

    (gdb) b 69
    Breakpoint 1 at 0x13c1: file xx.cpp, line 69.
    (gdb) r
    Starting program: /home/lester/tools/xxx/a.out 
    
    Breakpoint 1, main () at xx.cpp:69
    69	    return 0;
    (gdb) p d
    $1 = {_vptr.Drive = 0x555555557da0 <vtable for Drive+16>}
    (gdb) p (int64_t*)**(int64_t**)&d
    $2 = (int64_t *) 0x55555555543a <Drive::vf()>
    (gdb) 
    
    

    单继承

    class Base1
    {
    public:
        virtual void vb1f() {}
        virtual void vf() {}
    };
    
    class Drive : public Base1
    {
    public:
        virtual void vdf() {}
        virtual void vf() {}
        void f() {}
    };
    
    int main()
    {
        Drive d;
    
        return 0;
    }
    

    虚表中只有多个虚函数。顺序是父类,子类的顺序。其中注意到双方共有的虚函数 “vf”, 在虚表中子类的虚函数覆盖了父类的需函数。

    (gdb) b 70
    Breakpoint 1 at 0x13c1: file xx.cpp, line 70.
    (gdb) r
    Starting program: /home/lester/tools/xxx/a.out 
    
    Breakpoint 1, main () at xx.cpp:70
    70	    return 0;
    (gdb) p (int64_t*)*(*((int64_t**)&d)+0)
    $1 = (int64_t *) 0x55555555543a <Base1::vb1f()>
    (gdb) p (int64_t*)*(*((int64_t**)&d)+1)
    $2 = (int64_t *) 0x555555555452 <Drive::vf()>
    (gdb) p (int64_t*)*(*((int64_t**)&d)+2)
    $3 = (int64_t *) 0x555555555446 <Drive::vdf()>
    (gdb) 
    

    多继承

    class Base1
    {
    public:
        virtual void vb1f() {}
        virtual void vf() {}
    };
    
    class Base2
    {
    public:
        virtual void vb2f() {}
        virtual void vf() {}
    };
    
    class Drive : public Base1, Base2
    {
    public:
        virtual void vdf() {}
        virtual void vf() {}
        void f() {}
    };
    
    int main()
    {
        Drive d;
    
        return 0;
    }
    

    虚表中只有多个虚函数。顺序是父类Base1, 父类Base2,子类。

    查看第一个虚表:其中注意到双方共有的虚函数 “vf”, 在虚表中子类的虚函数覆盖了父类的需函数。另外子类的虚函数 ”vdf“ 被放在了第一个虚表的后面。

    查看第二个虚表:第二个虚表指针在地一个虚表指针后面。同样方式可以看到第二个虚表只有父类 Base2 的虚成员函数,而且共有的虚函数被子类的虚函数 vf 覆盖。

    (gdb) b 71
    Breakpoint 1 at 0x13cc: file xx.cpp, line 71.
    (gdb) r
    Starting program: /home/lester/tools/xxx/a.out 
    
    Breakpoint 1, main () at xx.cpp:71
    71	    return 0;
    (gdb) 
    (gdb) p d
    $10 = {<Base1> = {_vptr.Base1 = 0x555555557d28 <vtable for Drive+16>}, <Base2> = {_vptr.Base2 = 0x555555557d50 <vtable for Drive+56>}, <No data fields>}
    (gdb) p (int64_t*)*(*((int64_t**)&d)+0)
    $1 = (int64_t *) 0x555555555446 <Base1::vb1f()>
    (gdb) p (int64_t*)*(*((int64_t**)&d)+1)
    $2 = (int64_t *) 0x55555555546a <Drive::vf()>
    (gdb) p (int64_t*)*(*((int64_t**)&d)+2)
    $3 = (int64_t *) 0x55555555545e <Drive::vdf()>
    
    
    (gdb) p d
    $10 = {<Base1> = {_vptr.Base1 = 0x555555557d28 <vtable for Drive+16>}, <Base2> = {_vptr.Base2 = 0x555555557d50 <vtable for Drive+56>}, <No data fields>}
    (gdb) p (int64_t*)*((int64_t*)*(int64_t*)(*((int64_t**)&d)+1)+0)
    $12 = (int64_t *) 0x555555555452 <Base2::vb2f()>
    (gdb) p (int64_t*)*((int64_t*)*(int64_t*)(*((int64_t**)&d)+1)+1)
    $13 = (int64_t *) 0x555555555475 <non-virtual thunk to Drive::vf()>
    (gdb) 
    

    虚继承(菱形继承)

    单虚继承情况和单继承完全一样,这里忽略,直接描述虚继承的菱形继承情况

    class Base
    {
    public:
       virtual void vbbf() {}
       virtual void vbf() {}
    };
    
    class Base1 : virtual public Base
    {
    public:
       virtual void vb1f() {}
       virtual void vf() {}
    };
    
    class Base2 : virtual public Base
    {
    public:
       virtual void vb2f() {}
       virtual void vf() {}
    };
    
    class Drive : virtual public Base1, virtual public Base2
    {
    public:
       virtual void vdf() {}
       virtual void vf() {}
       void f() {}
    };
    
    int main()
    {
       Base1 b1;
       Base2 b2;
       Drive d;
    
       return 0;
    }
    

    虚多继承

    Breakpoint 1 at 0x1194: file xx.cpp, line 41.
    (gdb) r
    Starting program: /home/lester/Wdir/src/myself/test/a.out 
    
    Breakpoint 1, main () at xx.cpp:41
    41         return 0;
    (gdb) p (int64_t*)*((int64_t*)d+0)
    $1 = (int64_t *) 0x55555555520e <Base::vbbf()>
    (gdb) p (int64_t*)*((int64_t*)d+1)
    $2 = (int64_t *) 0x55555555521a <Base::vbf()>
    (gdb) p (int64_t*)*((int64_t*)d+2)
    $3 = (int64_t *) 0x555555555226 <Base1::vb1f()>
    (gdb) p (int64_t*)*((int64_t*)d+3)
    $4 = (int64_t *) 0x555555555262 <Drive::vf()>
    (gdb) p (int64_t*)*((int64_t*)d+4)
    $5 = (int64_t *) 0x555555555256 <Drive::vdf()>
    (gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+0)
    $7 = (int64_t *) 0x0
    (gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+1)
    $11 = (int64_t *) 0x0
    (gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+2)
    $8 = (int64_t *) 0x55555555523e <Base2::vb2f()>
    (gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+3)
    $9 = (int64_t *) 0x55555555526d <virtual thunk to Drive::vf()>
    (gdb) p (int64_t*)*((int64_t*)*((int64_t*)&d+1)+4)
    $10 = (int64_t *) 0x555555557ad8 <vtable for Drive+72>
    (gdb) 
    

    如上可以看到地一个虚表和预期完全一样,按照继承的顺序,虚函数的顺序存在虚表中。
    但是第二个虚表就不一样了,第二个虚表的前两个成员都是空的,并不是指向 Base 的虚函数,所以可以知道编译器在这里做了处理,避免了菱形继承中尴尬的情况。

  • 相关阅读:
    HDOJ 1846 Brave Game
    并查集模板
    HDU 2102 A计划
    POJ 1426 Find The Multiple
    POJ 3278 Catch That Cow
    POJ 1321 棋盘问题
    CF 999 C.Alphabetic Removals
    CF 999 B. Reversing Encryption
    string的基础用法
    51nod 1267 4个数和为0
  • 原文地址:https://www.cnblogs.com/sinpo828/p/13156042.html
Copyright © 2011-2022 走看看