• C++对象模型探索--04数据语义


    数据语义学

    数据成员绑定时机

    1. 编译器对成员函数的解析是整个类定义完毕后才开始的。因为只有整个类定义完毕后编译器才能够知道类的成员变量,才能根据时机在需
      要出现类成员变量的场合做出适当的解析(成员函数中解析成类中的成员变量,全局函数中解析成全局的变量
    2. 对于成员函数参数是在编译器第一次遇到这个类型的时候决定的

    进程内存空间布局

    不同的数据在内存中有不同的保存时机、保存位置。在执行程序时,总内存会被分为多个段,称为文本,未初始化的全局、初始化的全局、栈和堆段。将整个程序加载到文本段中,并在堆栈存储器中选择存储器到变量。

    High Addresses ---> .----------------------.
                        |      Environment     |
                        |----------------------|
                        |                      |   Functions and variable are declared
                        |         STACK        |   on the stack.
    	base pointer -> | - - - - - - - - - - -|
                        |           |          |
                        |           v          |
                        :                      :
                        .                      .   The stack grows down into unused space
                        .         Empty        .   while the heap grows up. 
                        .                      .
                        .                      .   (other memory maps do occur here, such 
                        .                      .    as dynamic libraries, and different memory
                        :                      :    allocate)
                        |           ^          |
                        |           |          |
    	   brk point -> | - - - - - - - - - - -|   Dynamic memory is declared on the heap
                        |          HEAP        |
                        |                      |
                        |----------------------|
                        |          BSS         |   Uninitialized data (BSS)
                        |----------------------|   
                        |          Data        |   Initialized data (DS)
                        |----------------------|
                        |          Text        |   Binary code
    Low Addresses ----> '----------------------'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    栈(STACK)
    1. 它位于较高的地址,与堆段的增长和收缩方向正好相反。
    2. 函数的局部变量存在于栈上
    3. 每个函数都有一个栈帧,调用函数时,将在栈中创建一个栈帧。栈帧包含函数的局部变量参数和返回值。
    4. 栈包含一个LIFO结构。函数变量在调用时被压入栈,返回时将函数变量从栈弹出。
    5. SP(栈指针)寄存器跟踪栈的顶部。
    堆(HEAP)
    1. 堆区域由进程中的所有共享库和动态加载的模块共享。它在堆栈的相反方向上增长和收缩。
    2. 用于在运行时分配内存。由内存管理函数(如malloc、calloc、free等)管理的堆区域,这些函数可以在内部使用brk和sbrk系统调用来调整其大小。
    未初始化的数据块(BSS)

    此段包含所有未初始化的全局和静态变量,所有变量都由零或者空指针初始化。程序加载器在加载程序时为BSS节分配内存。

    初始化的数据块(DS)

    此段包含显式初始化的全局变量和静态变量,大小由程序源代码中值的大小决定,在运行时不会更改。它具有读写权限,因此可以在运行时更改此段的变量值。

    代码段(TEXT)

    该段是一个只读段,包含已编译程序的二进制文件。该段是可共享的,因此对于文本编辑器等频繁执行的程序,内存中只需要一个副本

    C++单继承下内存布局分析
    #include 
    class A
    {
    public:
        int a;
        A() : a(0x1) {}
        virtual void foo() { std::cout << "A::foo()" << std::endl; }
        void bar() { std::cout << "A::bar()" << std::endl; }
    };
    
    class B : public A
    {
    public:
        int b;
        B() : A(), b(0x2) {}
        void foo() { std::cout << "B::foo()" << std::endl; }
    };
    
    class C : public B
    {
    public:
        int c;
        C() : B(), c(0x3) {}
        void foo() { std::cout << "C::foo()" << std::endl; }
    };
    
    int main(int argc, char **argv)
    {
        A a;
        B b;
        C c;
        B *p = &c;
        p->foo();
    
        std::cout << sizeof(int) << " " << sizeof(int *) << std::endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    g++ main.cpp -o main -std=c++14 -g
    gdb查看

    (gdb) b 29
    Breakpoint 1 at 0x120b: file main.cpp, line 29.
    (gdb) r
    Breakpoint 1, main (argc=1, argv=0x7fffffffdd48) at main.cpp:29
    29          A a;
    (gdb) n
    30          B b;
    (gdb) set print pretty on
    (gdb) set print vtbl on
    (gdb) p a
    $1 = {
      _vptr.A = 0x555555557d38 <vtable for A+16>,
      a = 1
    }
    (gdb) p/a &a
    $2 = 0x7fffffffdbf0
    (gdb) p/a &a.a
    $3 = 0x7fffffffdbf8
    (gdb) p sizeof(a)
    $4 = 16
    (gdb) x/2xg &a 
    0x7fffffffdbf0: 0x0000555555557d38      0x00007fff00000001
    (gdb) info vtbl a
    vtable for 'A' @ 0x555555557d38 (subobject @ 0x7fffffffdbf0):
    [0]: 0x555555555344 <A::foo()>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • _vptr.A:代表a对象所含有的虚函数表指针,0x555555557d38为第一个虚函数也即foo()的地址,真正虚函数表的起始地址为0x555555557d38 - 16,还会有一些虚函数表头信息,vptr 总是指向 虚函数表的第一个函数入口
    • 对象a所在的地址为0x7fffffffdbf0,整个对象占16个字节,其中8个字节为vptr虚函数表指针,4个字节为数据int a
    • vtable for ‘A’ @ 0x555555557d38

    gdb查看

    (gdb) n
    31          C c;
    (gdb) p b
    $6 = {
      <A> = {
        _vptr.A = 0x555555557d20 <vtable for B+16>,
        a = 1
      }, 
      members of B:
      b = 2
    }
    (gdb) p sizeof(b)
    $7 = 16
    (gdb) n
    32          B *p = &c;
    (gdb) p c
    $8 = {
      <B> = {
        <A> = {
          _vptr.A = 0x555555557d08 <vtable for C+16>,
          a = 1
        }, 
        members of B:
        b = 2
      }, 
      members of C:
      c = 3
    }
    (gdb) p sizeof(c)
    $9 = 24
    (gdb) info vtbl b
    vtable for 'B' @ 0x555555557d20 (subobject @ 0x7fffffffdc00):
    [0]: 0x5555555553ba <B::foo()>
    (gdb) info vtbl c
    vtable for 'C' @ 0x555555557d08 (subobject @ 0x7fffffffdc10):
    [0]: 0x555555555430 <C::foo()>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    如果class B中申明了新的虚函数(比如foo2),class B中依然只有一个虚函数表,只不过会把foo2加入到该表中。此时class A的虚函数表不会包含foo2。

    C++多重继承下内存布局分析
    #include 
    
    class A
    {
        int a;
        virtual void foo() { std::cout << "A::foo()" << std::endl; }
    };
    
    class B
    {
        int b;
        virtual void bar() { std::cout << "B::bar()" << std::endl; }
    };
    
    class C : public A, public B
    {
        int c;
        void foo() { std::cout << "C::foo()" << std::endl; }
        void bar() { std::cout << "C::bar()" << std::endl; }
    };
    
    int main(int argc, char **argv)
    {
        A a;
        B b;
        C c;
    
        std::cout << sizeof(int) << " " << sizeof(int *) << std::endl;
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    gdb查看

    gdb main 
    (gdb) b 28
    Breakpoint 1 at 0x122f: file main.cpp, line 28.
    (gdb) set print pretty on
    (gdb) set print vtbl on
    (gdb) set print object on
    (gdb) p a
    No symbol "a" in current context.
    (gdb) r
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
    
    Breakpoint 1, main (argc=1, argv=0x7fffffffdd48) at main.cpp:28
    28          std::cout << sizeof(int) << " " << sizeof(int *) << std::endl;
    (gdb) p a
    $1 = (A) {
      _vptr.A = 0x555555557d20 <vtable for A+16>,
      a = -134528544
    }
    (gdb) p b
    $2 = (B) {
      _vptr.B = 0x555555557d08 <vtable for B+16>,
      b = -135408993
    }
    (gdb) p/a c
    $3 = (C) {
      <A> = {
        _vptr.A = 0x555555557cd0 <vtable for C+16>,
        a = 0xfffffffff7eab60a
      }, 
      <B> = {
        _vptr.B = 0x555555557cf0 <vtable for C+48>,
        b = 0xfffffffff7fb3e88
      }, 
      members of C:
      c = 0x7fff
    --Type <RET> for more, q to quit, c to continue without paging--
    }
    (gdb) p sizeof(c)
    $4 = 32
    (gdb) x/5ag &c
    0x7fffffffdc00: 0x555555557cd0 <_ZTV1C+16>      0x7ffff7eab60a <_ZNSt9basic_iosIwSt11char_traitsIwEE15_M_cache_localeERKSt6locale+90>
    0x7fffffffdc10: 0x555555557cf0 <_ZTV1C+48>      0x7ffff7fb3e88 <_ZSt5wclog+8>
    0x7fffffffdc20: 0x7ffff7fb3940
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 数据成员int a, int b, int c都未初始化,此时是UB未定义行为
    • 对象c含有两个虚函数表指针_vptr.A和_vptr.B,占用32个字节内存,3个int数据成员,两个虚函数表指针
    • 对象c的内存布局为 c: vptr.A | a | vptr.B | b | c
    C++虚继承下内存布局分析
    #include 
    
    class A
    {
        int a;
        virtual void foo() { std::cout << "A::foo()" << std::endl; }
    };
    
    class B : virtual public A
    {
        int b;
        virtual void foo() { std::cout << "B::foo()" << std::endl; }
    };
    
    class C : virtual public A
    {
        int c;
        void foo() { std::cout << "C::foo()" << std::endl; }
    };
    
    class D : public B, public C
    {
        int d;
        virtual void foo() { std::cout << "D::foo()" << std::endl; }
    };
    
    int main(int argc, char **argv)
    {
        A a;
        B b;
        C c;
        D d;
    
        A *pa = &d;
        B *pb = &d;
        C *p c = &d;
        std::cout << sizeof(int) << " " << sizeof(int *) << std::endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    gdb查看

    gdb main
    (gdb) b 37
    Breakpoint 1 at 0x1247: file main.cpp, line 37.
    (gdb) r
    37          std::cout << sizeof(int) << " " << sizeof(int *) << std::endl;
    (gdb) set print pretty on
    (gdb) set print object on
    (gdb) set print vtbl on
    (gdb) p a
    $1 = (A) {
      _vptr.A = 0x555555557ce0 ,
      a = 0
    }
    (gdb) p b
    $2 = (B) {
       = {
        _vptr.A = 0x555555557cb8 ,
        a = -238370304
      }, 
      members of B:
      _vptr.B = 0x555555557c98 ,
      b = 0
    --Type  for more, q to quit, c to continue without paging--
    }
    (gdb) p c
    $3 = (C) {
       = {
        _vptr.A = 0x555555557c68 ,
        a = -134528544
      }, 
      members of C:
      _vptr.C = 0x555555557c48 ,
      c = -134529192
    }
    (gdb) p d
    $4 = (D) {
       = {
         = {
          _vptr.A = 0x555555557b70 ,
          a = -134529400
        }, 
        members of B:
        _vptr.B = 0x555555557b30 ,
        b = -135408993
      }, 
       = {
        members of C:
        _vptr.C = 0x555555557b50 ,
        c = -135612918
      }, 
      members of D:
      d = 32767
    }
    (gdb) p &d
    $5 = (D *) 0x7fffffffdbf0
    
    • 对象内存布局
      • a: _vptr.A | a
      • b: _vptr.A | a | _vptr.B | b
      • c: _vptr.A | a | _vptr.C | c
      • d: _vptr.A | a | _vptr.B | b | _vptr.C | c | d
    • A *pa = &d;B pb = &d;Cp c= &d;都指向d的起始地址&d = 0x7fffffffdbf0。假如d类里实现的虚函数都放在A的虚函数表里,没有实现的放在被继承的基类里面

    数据成员布局

    1. 普通成员变量的存储顺序是按照在类中定义的顺序从上到下来的
    2. 类定义中public、private、protected的数量不影响类对象的sizeof
    3. 边界调整,字节对齐

    数据成员存取

    • 静态成员变量的存取
      类的静态成员变量可以当作一个全局变量,它只是在类的空间内可见,引用时用类名::静态成员变量名,静态成员变量只有一个实体,保存在可执行文件的数据段。
    class A 
    {
    public:
        int m_a;
        int m_b;
        static int m_c; // 声明
    }
    
    int A::m_c = 0; // 定义
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 非静态的成员变量的存取
      对于普通成员变量的访问,编译器是把类对象的首地址加上成员变量的偏移值

    单一继承下的数据成员布局

    • 一个子类对象所包含的内容是它自己的成员+父类的成员的总和
    • 从偏移值看,父类成员先出现,然后才是子类成员,即子类对象中包含父类子对象
    #include 
    #include 
    
    class Base
    {
    public:
        int m_a;
        int m_b;
    };
    
    class Derived : public Base
    {
    public:
        int m_i;
        int m_j;
    };
    
    int main(int argc, char **argv)
    {
        // 打印成员变量偏移值
        printf("Base::m_a = %d\n", &Base::m_a);
        printf("Base::m_b = %d\n", &Base::m_b);
    
        printf("Derived::m_a = %d\n", &Derived::m_a);
        printf("Derived::m_b = %d\n", &Derived::m_b);
    
        printf("Derived::m_i = %d\n", &Derived::m_i);
        printf("Derived::m_j = %d\n", &Derived::m_j);
    
        Base b;    // m_a | m_b
        Derived d; // m_a | m_b | m_i | m_j
    
        return 0;
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    #include 
    #include 
    
    class Base1
    {
    public:
        int m_a;
        char m_c1;
    };
    
    class Base2 : public Base1
    {
    public:
        char m_c2;
    };
    
    class Base3 : public Base2
    {
    public:
        char m_c3;
    };
    
    int main(int argc, char **argv)
    {
        Base1 b1; //  8byte: 4 | 1 | 3padding
        Base2 b2; // 12byte: 4 | 1 | 3padding | 1 | 3padding
        Base3 b3; // 12byte: 4 | 1 | 3padding | 1 | 1 | 2padding
    	
        printf("sizeof(Base1) = %d\n", sizeof(Base1));
        printf("sizeof(Base2) = %d\n", sizeof(Base2));
        printf("sizeof(Base3) = %d\n", sizeof(Base3));
    	// 打印成员变量偏移值
        printf("Base3::m_a = %d\n", &Base3::m_a);
        printf("Base3::m_c1 = %d\n", &Base3::m_c1);
        printf("Base3::m_c2 = %d\n", &Base3::m_c2);
        printf("Base3::m_c3 = %d\n", &Base3::m_c3);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    输出结果

    sizeof(Base1) = 8
    sizeof(Base2) = 12
    sizeof(Base3) = 12
    Base3::m_a = 0
    Base3::m_c1 = 4
    Base3::m_c2 = 8
    Base3::m_c3 = 9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    单类单继承虚函数下的数据成员布局

    单个类带虚函数的数据成员布局

    类中引入虚函数时会有额外的成本付出:

    1. 编译时编译器会产生虚函数表
    2. 对象中会产生虚函数表指针vptr,用以指向虚函数表
    3. 增加或扩展构造函数,增加给虚函数表指针vptr,让vptr指向虚函数表
    4. 如果多重继承,比如继承2个父类,每个父类都有虚函数的话,每个父类都有vptr,那么继承时,子类会把这2个vptr都继承过来,如果子类还有自己额外的虚函数,子类与第一个基类共用一个vptr
    class Base
    {
    public:
        int m_i;
        int m_j;
    
        Base() {}
        ~Base(){}
        virtual void func() {}
    };
    //内存布局为
    // vptr | m_i | m_j
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    单一继承下父类带虚函数的数据成员布局

    #include 
    #include 
    
    class Base
    {
    public:
        int m_i;
    
        Base() {}
        ~Base() {}
        virtual void b_func() {}
    };
    
    class Derived : public Base
    {
    public:
        int m_a;
        int m_b;
    
        Derived() {}
        ~Derived() {}
    
        virtual void d_func() {}
    };
    
    int main(int argc, char **argv)
    {
        printf("sizeof(Base) = %d\n", sizeof(Base));
        printf("sizeof(Derived) = %d\n", sizeof(Derived));
        Base b;
        Derived d;
    
        printf("Base::m_i = %d\n", &Base::m_i);
        printf("Derived::m_i = %d\n", &Derived::m_i);
        printf("Derived::m_a = %d\n", &Derived::m_a);
        printf("Derived::m_b = %d\n", &Derived::m_b);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    输出结果为

    sizeof(Base) = 16
    sizeof(Derived) = 24
    Base::m_i = 8
    Derived::m_i = 8
    Derived::m_a = 12
    Derived::m_b = 16
    
    //内存布局为
    //Base: vptr | m_i | padding4
    //Derived: vptr | m_i | m_a | m_b | padding4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    单一继承下父类不带虚函数的数据成员布局

    #include 
    #include 
    
    class Base
    {
    public:
        int m_i;
    
        Base() {}
        ~Base() {}
    };
    
    class Derived : public Base
    {
    public:
        int m_a;
        int m_b;
    
        Derived() {}
        ~Derived() {}
    
        virtual void d_func() {}
    };
    
    int main(int argc, char **argv)
    {
        printf("sizeof(Base) = %d\n", sizeof(Base));
        printf("sizeof(Derived) = %d\n", sizeof(Derived));
        Derived d;
    
        d.m_i = 1;
        d.m_a = 2;
        d.m_b = 3;
        
        printf("Base::m_i = %d\n", &Base::m_i);
        printf("Derived::m_i = %d\n", &Derived::m_i);
        printf("Derived::m_a = %d\n", &Derived::m_a);
        printf("Derived::m_b = %d\n", &Derived::m_b);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    gdb查看

     gdb main
    (gdb) set print pretty on
    (gdb) set print object on
    (gdb) set print vtbl on
    (gdb) b 35
    Breakpoint 1 at 0x123f: file main.cpp, line 35.
    (gdb) r
    sizeof(Base) = 4
    sizeof(Derived) = 24
    
    Breakpoint 1, main (argc=1, argv=0x7fffffffdd48) at main.cpp:35
    35          printf("Base::m_i = %d\n", &Base::m_i);
    (gdb) p/a &d
    $1 = 0x7fffffffdc00
    (gdb) x/10xw &d
    0x7fffffffdc00: 0x55557d40      0x00005555      0x00000001      0x00000002
    0x7fffffffdc10: 0x00000003      0x00007fff      0x342a4e00      0xa0066d24
    0x7fffffffdc20: 0xf7fb3940      0x00007fff
    (gdb) info vtbl d
    vtable for 'Derived' @ 0x555555557d40 (subobject @ 0x7fffffffdc00):
    [0]: 0x5555555553e4 <Derived::d_func()>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    从gdb调试信息可以看出
    d的vptr为0x555555557d40 即从虚拟地址0x7fffffffdc00~0x7fffffffdc07
    地址0x7fffffffdc08~0x7fffffffdc0b的值为0x00000001即d.m_i的值
    地址0x7fffffffdc0c~0x7fffffffdc0f的值为0x00000002即d.m_a的值
    地址0x7fffffffdc10~0x7fffffffdc13的值为0x00000003即d.m_b的值
    由此可得d的内存布局为:vptr | m_i | m_a | m_b | padding4

    多重继承且父类都带虚函数的数据成员布局

    #include 
    #include 
    
    class Base1
    {
    public:
        int m_b1;
    
        Base1() { printf("    Base1::Base1() 的this指针是:%p\n", this); }
        ~Base1() {}
        virtual void func_b1() {}
    };
    
    class Base2
    {
    public:
        int m_b2;
    
        Base2() { printf("    Base2::Base2() 的this指针是:%p\n", this); }
        ~Base2() {}
        virtual void func_b2() {}
    };
    
    class Derived : public Base1, public Base2
    {
    public:
        int m_a;
        int m_b;
    
        Derived()
        {
            printf("Derived::Derived() 的this指针是:%p\n", this);
        }
        ~Derived() {}
    
        virtual void d_func() {}
    };
    
    int main(int argc, char **argv)
    {
        printf("sizeof(Base1) = %d\n", sizeof(Base1));
        printf("sizeof(Base2) = %d\n", sizeof(Base2));
        printf("sizeof(Derived) = %d\n", sizeof(Derived));
    
        Derived d;
        d.m_b1 = 1;
        d.m_b2 = 2;
        d.m_a = 3;
        d.m_b = 4;
    
        printf("Base1::m_b1 = %d\n", &Base1::m_b1);
        printf("Base2::m_b2 = %d\n", &Base2::m_b2);
    
        printf("Derived::m_b1 = %d\n", &Derived::m_b1);
        printf("Derived::m_b2 = %d\n", &Derived::m_b2);
        printf("Derived::m_a = %d\n", &Derived::m_a);
        printf("Derived::m_b = %d\n", &Derived::m_b);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    gdb查看

    gdb main
    
    (gdb) set print pretty on
    (gdb) set print object on
    (gdb) set print vtbl on
    (gdb) b 51
    Breakpoint 1 at 0x125f: file main.cpp, line 51.
    (gdb) r
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
    sizeof(Base1) = 16
    sizeof(Base2) = 16
    sizeof(Derived) = 40
        Base1::Base1() 的this指针是:0x7fffffffdbf0
        Base2::Base2() 的this指针是:0x7fffffffdc00
    Derived::Derived() 的this指针是:0x7fffffffdbf0
    
    Breakpoint 1, main (argc=1, argv=0x7fffffffdd48) at main.cpp:51
    51          printf("Base1::m_b1 = %d\n", &Base1::m_b1);
    (gdb) p d
    $1 = (Derived) {
      <Base1> = {
        _vptr.Base1 = 0x555555557cd0 <vtable for Derived+16>,
        m_b1 = 1
      }, 
      <Base2> = {
        _vptr.Base2 = 0x555555557cf0 <vtable for Derived+48>,
        m_b2 = 2
      }, 
      members of Derived:
      m_a = 3,
      m_b = 4
    }
    (gdb) x/10xw &d
    0x7fffffffdbf0: 0x55557cd0      0x00005555      0x00000001      0x00007fff
    0x7fffffffdc00: 0x55557cf0      0x00005555      0x00000002      0x00000003
    0x7fffffffdc10: 0x00000004      0x00007fff
    (gdb) info vtbl d
    vtable for 'Derived' @ 0x555555557cd0 (subobject @ 0x7fffffffdbf0):
    [0]: 0x55555555540c <Base1::func_b1()>
    [1]: 0x555555555576 <Derived::d_func()>
    
    vtable for 'Base2' @ 0x555555557cf0 (subobject @ 0x7fffffffdc00):
    [0]: 0x555555555476 <Base2::func_b2()>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    从gdb调试信息可以看出
    Base1的虚函数指针vptr1和继承Derived的虚函数指针vptr同在0x7fffffffdbf0处,即继承类和多个基类的第一个类共用一个vptr
    地址0x7fffffffdbf0~0x7fffffffdbf7的值为0x0000555555557cd0即_vptr.Base1
    地址0x7fffffffdbf8~0x7fffffffdbfb的值为0x00000001即m_b1的值
    地址0x7fffffffdbfc~0x7fffffffdbff的值为4字节的填充
    地址0x7fffffffdc00~0x7fffffffdc07的值为0x0000555555557cf0即_vptr.Base2
    地址0x7fffffffdc08~0x7fffffffdc0b的值为0x00000002即m_b2的值
    地址0x7fffffffdc0c~0x7fffffffdc0f的值为0x00000003即m_a的值
    地址0x7fffffffdc10~0x7fffffffdc13的值为0x00000004即m_b的值
    由此可得d的内存布局为:_vptr.Base1 | m_b1 | 4padding |_vptr.Base2 | m_b2 | m_a | m_b | padding4

    虚基类问题剖析

    虚基类(虚继承/虚派生)问题的提出

    传统多重继承的问题:空间问题效率问题二义性问题

    
    	|-----------|   |-----------|
    	|    Base   |   |    Base   |
    	|-----------|   |-----------|
    		  |               |
    		 \|/             \|/ 
    	|----------|     |----------|
    	| Derived1 |     | Derived2 |
    	|----------|     |----------|
    		 |               |	
    		 |---------------|           
    				|
    			   \|/
    		   |----------|
    		   |     C    |
    		   |----------|
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    #include 
    #include 
    
    class Base
    {
    public:
        int m_b1;
    };
    
    class Derived1 : public Base
    {
    public:
    };
    
    class Derived2 : public Base
    {
    public:
    };
    
    class C : public Derived1, public Derived2
    {
    };
    
    int main(int argc, char **argv)
    {
        printf("sizeof(Base) = %ld\n", sizeof(Base));
        printf("sizeof(Derived1) = %ld\n", sizeof(Derived1));
        printf("sizeof(Derived2) = %ld\n", sizeof(Derived2));
        printf("sizeof(C) = %ld\n", sizeof(C));
    
        C c; // c的内存布局为:m_b1 | m_b1
        c.Derived1::m_b1 = 1;
        c.Derived2::m_b1 = 1;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    虚基类初探

    虚基类表vbtable(virtual base table)
    虚基类表指针vbptr(virtual base table pointer)

    2层结构时虚基类表内容分析

    示例1

    
    			|-----------|
    			|    Base   |
    			|-----------|
    		          | 
    		   ---------------
    virtual   |               |  virtual
    		 \|/             \|/ 
    	|----------|     |----------|
    	| Derived1 |     | Derived2 |
    	|----------|     |----------|
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    #include 
    #include 
    
    class Base
    {
    public:
        int m_b;
    };
    
    class Derived1 : virtual public Base
    {
    public:
        int m_d1;
    };
    
    class Derived2 : virtual public Base
    {
    public:
        int m_d2;
    };
    
    int main(int argc, char **argv)
    {
        printf("sizeof(Base) = %ld\n", sizeof(Base));
        printf("sizeof(Derived1) = %ld\n", sizeof(Derived1));
    
        Derived1 d1;
        d1.m_b = 0x1111;
        d1.m_d1 = 0x2222;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    gdb查看

    gdb main
    (gdb) b 31
    Breakpoint 1 at 0x1230: file main.cpp, line 32.
    (gdb) set print pretty on
    (gdb) r
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
    sizeof(Base) = 4
    sizeof(Derived1) = 16
    
    Breakpoint 1, main (argc=1, argv=0x7fffffffdd48) at main.cpp:32
    32          return 0;
    (gdb) p &d1
    $1 = (Derived1 *) 0x7fffffffdc10
    (gdb) p d1
    $2 = {
      <Base> = {
        m_b = 4369
      }, 
      members of Derived1:
      _vptr.Derived1 = 0x555555557d58 <VTT for Derived1>,
      m_d1 = 8738
    }
    (gdb) x/2xg 0x7fffffffdc10
    0x7fffffffdc10: 0x0000555555557d58      0x0000111100002222
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    其中:
    sizeof(Derived1) = 16, &d=0x7fffffffdc10
    0x7fffffffdc10~0x7fffffffdc17内容为0x0000555555557d58即_vptr.Derived1
    0x7fffffffdc18~0x7fffffffdc1b内容为00002222即d1.m_d1 = 0x2222;
    0x7fffffffdc1c~0x7fffffffdc1f内容为00001111即d1.m_b = 0x1111;
    由此可得d1的内存布局为: vptr | m_d1 | m_b(虚基类对象大小)
    virtual虚继承之后,Derived1、Derived2会被编译器插入一个虚基类表指针

    示例2

    
    	|-----------|   |-----------|
    	|   Base1   |   |    Base2  |
    	|-----------|   |-----------|
     virtual |               | public
    		 |---------------|           
    				|
    			   \|/
    		   |----------|
    		   | Derived1 |
    		   |----------|
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    #include 
    #include 
    
    class Base1
    {
    public:
        int m_b1;
    };
    
    class Base2
    {
    public:
        int m_b2;
    };
    
    class Derived1 : virtual public Base1, public Base2
    {
    public:
        int m_d1;
    };
    
    int main(int argc, char **argv)
    {
        printf("sizeof(Base1) = %ld\n", sizeof(Base1));
        printf("sizeof(Base2) = %ld\n", sizeof(Base2));
        printf("sizeof(Derived1) = %ld\n", sizeof(Derived1));
    
        Derived1 d1;
        d1.m_b1 = 0xb1;
        d1.m_b2 = 0xb2;
        d1.m_d1 = 0x2222;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    gdb查看

    gdb main 
    (gdb) b 33
    Breakpoint 1 at 0x1237: file main.cpp, line 33.
    (gdb) set print pretty on
    (gdb) r
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
    sizeof(Base1) = 4
    sizeof(Base2) = 4
    sizeof(Derived1) = 24
    
    Breakpoint 1, main (argc=1, argv=0x7fffffffdd48) at main.cpp:33
    warning: Source file is more recent than executable.
    33          return 0;
    (gdb) p d1
    $1 = {
      <Base1> = {
        m_b1 = 177
      }, 
      <Base2> = {
        m_b2 = 178
      }, 
      members of Derived1:
      _vptr.Derived1 = 0x555555557d38 <VTT for Derived1>,
      m_d1 = 8738
    }
    (gdb) p d1
    $2 = {
      <Base1> = {
        m_b1 = 177
      }, 
      <Base2> = {
        m_b2 = 178
      }, 
      members of Derived1:
      _vptr.Derived1 = 0x555555557d38 <VTT for Derived1>,
      m_d1 = 8738
    }
    (gdb) p &d1
    $3 = (Derived1 *) 0x7fffffffdc10
    (gdb) x/6xw 0x7fffffffdc10
    0x7fffffffdc10: 0x55557d38      0x00005555      0x000000b2      0x00002222
    0x7fffffffdc20: 0x000000b1      0x00007fff
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    其中:
    sizeof(Derived1) = 24, &d1=0x7fffffffdc10
    0x7fffffffdc10~0x7fffffffdc17内容为0x0000555555557d38即_vptr.Derived1
    0x7fffffffdc18~0x7fffffffdc1b内容为0x000000b2即d1.m_b2 = 0x000000b2;
    0x7fffffffdc1c~0x7fffffffdc1f内容为0x00002222即d1.m_d1 = 0x2222;
    0x7fffffffdc20~0x7fffffffdc23内容为0x000000b1即d1.m_b1 = 0x000000b1;
    由此可得d1的内存布局为: vptr | m_b2 | m_d1 | m_b1(虚基类对象大小) | padding4 |

    3层结构时虚基类表内容分析
    
    			|-----------|
    			|    Base   |
    			|-----------|
    		          | 
    		   ---------------
    virtual   |               |  virtual
    		 \|/             \|/ 
    	|----------|     |----------|
    	| Derived1 |     | Derived2 |
    	|----------|     |----------|
    		 |               |	
    		 |---------------|           
    				|
    			   \|/
    		   |----------|
    		   |     C    |
    		   |----------|
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    #include 
    #include 
    
    class Base
    {
    public:
        int m_b1;
    };
    
    class Derived1 : virtual public Base
    {
    public:
        int m_d1;
    };
    
    class Derived2 : virtual public Base
    {
    public:
        int m_d2;
    };
    
    class C : public Derived1, public Derived2
    {
    public:
        int m_c;
    };
    
    int main(int argc, char **argv)
    {
        printf("sizeof(Base) = %ld\n", sizeof(Base));
        printf("sizeof(Derived1) = %ld\n", sizeof(Derived1));
        printf("sizeof(Derived2) = %ld\n", sizeof(Derived2));
        printf("sizeof(C) = %ld\n", sizeof(C));
    
        C c;// c的内存布局为:vbptr1(from Derived1) | m_d1 | vbptr2(from Derived2) | m_d2 | m_c | m_b1(虚基类对象)
        c.m_b1 = 0xb1;
        c.m_d1 = 0xd1;
        c.m_d2 = 0xd2;
        c.m_c = 0xc;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    gdb查看

     gdb main
    (gdb) b 41
    Breakpoint 1 at 0x1257: file main.cpp, line 41.
    (gdb) r
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
    sizeof(Base) = 4
    sizeof(Derived1) = 16
    sizeof(Derived2) = 16
    sizeof(C) = 40
    
    Breakpoint 1, main (argc=1, argv=0x7fffffffdd48) at main.cpp:41
    41          return 0;
    (gdb) p &c
    $1 = (C *) 0x7fffffffdc00
    (gdb) set print pretty on
    (gdb) p c
    $3 = {
      <Derived1> = {
        <Base> = {
          m_b1 = 177
        }, 
        members of Derived1:
        _vptr.Derived1 = 0x555555557c98 <vtable for C+24>,
        m_d1 = 209
      }, 
      <Derived2> = {
        members of Derived2:
        _vptr.Derived2 = 0x555555557cb0 <VTT for C>,
        m_d2 = 210
      }, 
      members of C:
      m_c = 12
    }
    (gdb) x/10xw 0x7fffffffdc00
    0x7fffffffdc00: 0x55557c98      0x00005555      0x000000d1        0x00007fff
    0x7fffffffdc10: 0x55557cb0      0x00005555      0x000000d2        0x0000000c
    0x7fffffffdc20: 0x000000b1      0x00007fff
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    其中:
    sizeof© = 40, &c=0x7fffffffdc00
    0x7fffffffdc00~0x7fffffffdc07内容为0x0000555555557c98即_vptr.Derived1 = 0x555555557c98,由于多继承派生类与第一个基类共用一个vptr,即c的vptr=0x555555557c98
    0x7fffffffdc08~0x7fffffffdc0b内容为0x000000d1即c.m_d1 = 0x000000d1;
    0x7fffffffdc10~0x7fffffffdc17内容为0x0000555555557cb0即_vptr.Derived2 = 0x555555557cb0;
    0x7fffffffdc18~0x7fffffffdc1b内容为0x000000d2即c.m_d2 = 0xd2;
    0x7fffffffdc1c~0x7fffffffdc1f内容为0x0000000c即c.m_c = 0xc;
    0x7fffffffdc20~0x7fffffffdc23内容为0x000000b1即c.m_b1 = 0xb1;
    由此可得c的内存布局为: vptr1 | m_d1 | padding4 | vptr2 | m_d2 | m_c | m_b1(虚基类对象大小) | padding4 |

  • 相关阅读:
    web安全渗透之钓鱼网站提权
    力扣每日一题
    注意!各国政府纷纷出台的AI安全监管措施,主要集中在六方面
    第14章 Linux的shell编程
    c#设计模式-结构型模式 之适配器模式
    Spring中自定义依赖注入对象注入Controller中,优雅的解决用户鉴权问题(HandlerInterceptorAdapter)
    Docker容器
    xilinx fpga ultrascale 器件GTX参考时钟注意点
    01.Singleton Pattern 单例模式
    【单目3D目标检测】GUPNet论文精读与代码解析
  • 原文地址:https://blog.csdn.net/xxboy61/article/details/128055934