C++对象模型探索--04数据语义-创新互联

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

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

成都创新互联-专业网站定制、快速模板网站建设、高性价比源汇网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式源汇网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖源汇地区。费用合理售后完善,10多年实体公司更值得信赖。
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 ---->'----------------------'
栈(STACK)
  1. 它位于较高的地址,与堆段的增长和收缩方向正好相反。
  2. 函数的局部变量存在于栈上
  3. 每个函数都有一个栈帧,调用函数时,将在栈中创建一个栈帧。栈帧包含函数的局部变量参数和返回值。
  4. 栈包含一个LIFO结构。函数变量在调用时被压入栈,返回时将函数变量从栈弹出。
  5. SP(栈指针)寄存器跟踪栈的顶部。
堆(HEAP)
  1. 堆区域由进程中的所有共享库和动态加载的模块共享。它在堆栈的相反方向上增长和收缩。
  2. 用于在运行时分配内存。由内存管理函数(如malloc、calloc、free等)管理的堆区域,这些函数可以在内部使用brk和sbrk系统调用来调整其大小。
未初始化的数据块(BSS)

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

初始化的数据块(DS)

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

代码段(TEXT)

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

C++单继承下内存布局分析
#includeclass 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;
}

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,
  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
  • _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 = {= {_vptr.A = 0x555555557d20,
    a = 1
  }, 
  members of B:
  b = 2
}
(gdb) p sizeof(b)
$7 = 16
(gdb) n
32          B *p = &c;
(gdb) p c
$8 = {= {= {  _vptr.A = 0x555555557d08,
      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(gdb) info vtbl c
vtable for 'C' @ 0x555555557d08 (subobject @ 0x7fffffffdc10):
[0]: 0x555555555430

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

C++多重继承下内存布局分析
#includeclass 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;
}

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,
  a = -134528544
}
(gdb) p b
$2 = (B) {_vptr.B = 0x555555557d08,
  b = -135408993
}
(gdb) p/a c
$3 = (C) {= {_vptr.A = 0x555555557cd0,
    a = 0xfffffffff7eab60a
  }, 
   = {_vptr.B = 0x555555557cf0,
    b = 0xfffffffff7fb3e88
  }, 
  members of C:
  c = 0x7fff
--Typefor 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
C++虚继承下内存布局分析
#includeclass 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;
}

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
--Typefor 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;Bpb = &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; // 定义
  • 非静态的成员变量的存取
    对于普通成员变量的访问,编译器是把类对象的首地址加上成员变量的偏移值
单一继承下的数据成员布局
  • 一个子类对象所包含的内容是它自己的成员+父类的成员的总和
  • 从偏移值看,父类成员先出现,然后才是子类成员,即子类对象中包含父类子对象
#include#includeclass 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;
} 
#include#includeclass 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;
}

输出结果

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. 对象中会产生虚函数表指针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
单一继承下父类带虚函数的数据成员布局
#include#includeclass 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;
}

输出结果为

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
单一继承下父类不带虚函数的数据成员布局
#include#includeclass 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;
}

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

从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#includeclass 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;
}

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) {= {_vptr.Base1 = 0x555555557cd0,
    m_b1 = 1
  },= {_vptr.Base2 = 0x555555557cf0,
    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[1]: 0x555555555576vtable for 'Base2' @ 0x555555557cf0 (subobject @ 0x7fffffffdc00):
[0]: 0x555555555476

从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    |
		   |----------|
#include#includeclass 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;
}
虚基类初探

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

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

示例1

	|-----------|
			|    Base   |
			|-----------|
		          | 
		   ---------------
virtual   |               |  virtual
		 \|/             \|/ 
	|----------|     |----------|
	| Derived1 |     | Derived2 |
	|----------|     |----------|
#include#includeclass 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;
}

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 = {= {m_b = 4369
  }, 
  members of Derived1:
  _vptr.Derived1 = 0x555555557d58,
  m_d1 = 8738
}
(gdb) x/2xg 0x7fffffffdc10
0x7fffffffdc10: 0x0000555555557d58      0x0000111100002222

其中:
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 |
		   |----------|
#include#includeclass 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;
}

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 = {= {m_b1 = 177
  },= {m_b2 = 178
  }, 
  members of Derived1:
  _vptr.Derived1 = 0x555555557d38,
  m_d1 = 8738
}
(gdb) p d1
$2 = {= {m_b1 = 177
  },= {m_b2 = 178
  }, 
  members of Derived1:
  _vptr.Derived1 = 0x555555557d38,
  m_d1 = 8738
}
(gdb) p &d1
$3 = (Derived1 *) 0x7fffffffdc10
(gdb) x/6xw 0x7fffffffdc10
0x7fffffffdc10: 0x55557d38      0x00005555      0x000000b2      0x00002222
0x7fffffffdc20: 0x000000b1      0x00007fff

其中:
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    |
		   |----------|
#include#includeclass 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;
}

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 = {= {= {  m_b1 = 177
    }, 
    members of Derived1:
    _vptr.Derived1 = 0x555555557c98,
    m_d1 = 209
  },= {members of Derived2:
    _vptr.Derived2 = 0x555555557cb0,
    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

其中:
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 |

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


文章名称:C++对象模型探索--04数据语义-创新互联
分享URL:
http://csdahua.cn/article/psipj.html
扫二维码与项目经理沟通

我们在微信上24小时期待你的声音

解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流