`
weiyinchao88
  • 浏览: 1185020 次
文章分类
社区版块
存档分类
最新评论

C++对象的内存布局(下)

 
阅读更多
1. 仅有一个虚函数的类
class C010
{
virtual void foo(){};
}; // sizeof = 4
输出结果为:
The size of C010 is 4
The detail of C010 is 50 d0 46 00
当一个类中有虚函数时,编译器会为该类生成一个虚函数表,并在它的每一个对象中插入一个指向该虚函数表的指针,通常这个指针是插在对象的起始位置。所谓的虚函数表实际就是一个指针数组,其中的指针指向真正的函数起始地址。
在本次的测试输出中,可以看到虚指针指向的地址为0x0046d050,即虚表入口地址。
2. 含有成员变量与成员函数的带有虚函数的类
class C011
{
private:
char c;
int i;
public:
C011(){c = 0x01;i = 7;};
virtual void foo(){};
}; // sizeof = 12
输出结果为:
The size of C011 is 12
The detail of C011 is 50 d0 46 00 01 cc cc cc 07 00 00 00
如果声明两个C011类的实例对象obj1obj2,打印它们的信息如下:
obj1 : objadr:0012FF74 vpadr:0012FF74 vtadr:0046D064 vtival(0):00401249
obj2 : objadr:0012FF68 vpadr:0012FF68 vtadr:0046D064 vtival(0):00401249
(注:第一列为对象名,第二列(objadr)为对象的内存地址,第三列(vpadr)为虚表指针地址,第四列(vtadr)为虚表的地址,第五列(vtival(n))为虚表中的条目的值,n为条目的索引,从0开始。后同)
对象地址不同,虚表指针(vpadr)位于对象的起始位置,所以它的地址和对象相同。两个对象的虚表指针指向的是同一个虚表,因此(vtadr)的值相同,虚表中的第一条目(vtival(0))的值当然也一样。
3. 添加了普通成员的派生类
class C012:public C011
{
}; // sizeof = 12
输出结果为:
The size of C011 is 12
The detail of C011 is 58 d0 46 00 01 cc cc cc 07 00 00 00
The size of C012 is 12
The detail of C012 is 5c d0 46 00 01 cc cc cc 07 00 00 00
每个类维持自己持有的一张虚表,因此C011C012的虚表入口地址是不一样的。
但是C011中存储的内容都被完整地继承了下来。
class C013:public C011
{
private:
short s;
public:
C013(){s = 25;};
}; // sizeof = 16
输出结果为:
The size of C011 is 12
The detail of C011 is 58 d0 46 00 01 cc cc cc 07 00 00 00
The size of C013 is 16
The detail of C013 is 5c d0 46 00 01 cc cc cc 07 00 00 00 19 00 cc cc
如果声明C011C013的两个实例对象,打印它们的信息如下:
obj1 : objadr:0012FF74 vpadr:0012FF74 vtadr:0046D064 vtival(0):004011F4
obj3 : objadr:0012FF64 vpadr:0012FF64 vtadr:0046D068 vtival(0):004011F4
虽然它们的虚表不同,但是其中的0号表项中所存储的函数foo的入口地址却是一样的,这是因为在派生类中没有对基类的虚函数进行重写,因此仍将调用基类的实现
派生类中添加了一些普通成员后,除了虚表指针指向了派生类自己的虚表之外,其余规律都与普通类没有区别
4. 添加了新的虚成员函数的派生类
class C014
{
char c;
public:
C014(){c = 0x77;};
virtual vfun1(){printf("base func1.");};
virtual vfun2(){};
}; // sizeof = 8
class C015:public C014
{
int i;
public:
C015(){i = 1024;};
virtual vfun1(){printf("derived func1.");};
}; // sizeof = 12
在基类C014中声明了两个虚函数vfun1和vfun2,并且在派生类C015中将vfun1重写。
输出结果为:
The size of C014 is 8
The detail of C014 is a0 f0 46 00 77 cc cc cc
The size of C015 is 12
The detail of C015 is bc f0 46 00 77 cc cc cc 00 04 00 00
obj4 : objadr:0012FF5C vpadr:0012FF5C vtadr:0046F0A0 vtival(0):0040129E
obj4 : objadr:0012FF5C vpadr:0012FF5C vtadr:0046F0A0 vtival(1):00401307
obj5 : objadr:0012FF50 vpadr:0012FF50 vtadr:0046F0BC vtival(0):00401082
obj5 : objadr:0012FF50 vpadr:0012FF50 vtadr:0046F0BC vtival(1):00401307
基类和派生类的虚指针分别指向各自的虚表,虚表中的第0号表项分别指向不同的函数实现,这是因为派生类重写覆盖的缘故。第1号表项的内容是一样的,派生类继承基类的实现。
如果将上例中派生类C015的实现中增加一个新的虚成员函数vitrual void vfun3(){};,新的输出结果变为:
The size of C014 is 8
The detail of C014 is a0 f0 46 00 77 cc cc cc
The size of C015 is 12
The detail of C015 is bc f0 46 00 77 cc cc cc 00 04 00 00
obj4 : objadr:0012FF5C vpadr:0012FF5C vtadr:0046F0A0 vtival(0):004012A3
obj4 : objadr:0012FF5C vpadr:0012FF5C vtadr:0046F0A0 vtival(1):0040130C
obj5 : objadr:0012FF50 vpadr:0012FF50 vtadr:0046F0BC vtival(0):00401082
obj5 : objadr:0012FF50 vpadr:0012FF50 vtadr:0046F0BC vtival(1):0040130C
obj5 : objadr:0012FF50 vpadr:0012FF50 vtadr:0046F0BC vtival(2):00401267
派生类中仍然只有一个虚指针,只是添加了一条新的表项,指向vfun3的入口地址
5. 多重继承
class base1
{
public:
base1() : c(0x01) {}
virtual void foo1() { c = 0x02; }
char c;
};
class base2
{
public:
base2() : c(0x02) {}
virtual void foo2() {}
char c;
};
class derived : public base1, public base2
{
};
输出结果为:
The size of derived is 16
The detail of derived is 70 d0 46 00 01 cc cc cc 6c d0 46 00 02 cc cc cc
仍然跟普通类的多重继承一样,派生类derived先将左基类base1的内容继承下来,接着是base2的内容。因此派生类一共有两个虚指针,指向两张不同的虚表,之所以使用这种对象布局,主要是在处理类型的动态转换时更方便调整指针。
重写虚函数并添加新的虚函数的派生类:
struct derived_manip : public base1, public base2
{
public:
void foo2(){printf("hello world!");};
virtual int foo3(){return 5;};
}; // sizeof = 16
输出结果为:
The size of derived_manip is 16
The detail of derived_manip is b8 f0 46 00 01 cc cc cc b4 f0 46 00 02 cc cc cc
派生类中仍然只有两个虚指针指向两个不同的虚表。新添加的foo3被放进了第一个虚指针所指向的虚表的尾部
d : objadr:0012FF60 vpadr:0012FF60 vtadr:0046F0B8 vtival(0):0040113B
d : objadr:0012FF60 vpadr:0012FF60 vtadr:0046F0B8 vtival(1):00401217
d : objadr:0012FF60 vpadr:0012FF68 vtadr:0046F0B4 vtival(0):0040114F
6. 多层继承
class level_1
{
char c1;
public:
level_1(){c1 = 0x00;};
virtual void set1(){};
};
class level_2 : public level_1
{
char c2;
public:
level_2(){c2 = 0x01;};
virtual void set2(){};
};
class level_3 : public level_2
{
char c3;
public:
level_3(){c3 = 0x02;};
virtual void set3(){};
};
输出结果为:
The size of level_3 is 16
The detail of level_3 is 54 d0 46 00 00 cc cc cc 01 cc cc cc 02 cc cc cc
多层继承只保存从最顶层基类继承而来的唯一一个虚指针。这也是遵循继承规则的必然结果。
7. 多重与多层继承
class L1
{
char c1;
public:
L1(){c1 = 0x00;};
virtual void set1(){};
};
class L2
{
char c2;
public:
L2(){c2 = 0x01;};
virtual void set2(){};
};
class L3 : public L1, public L2
{
char c3;
public:
L3(){c3 = 0x02;};
virtual void set3(){};
};
class L4
{
char c4;
public:
L4(){c4 = 0x03;};
virtual void set4(){};
};
class L5 : public L3, public L4
{
char c5;
public:
L5(){c5 = 0x04;};
virtual void set5(){};
};
输出结果为:
The size of L5 is 32
The detail of L5 is 54 d0 46 00 00 cc cc cc 50 d0 46 00 01 cc cc cc 02 cc cc cc
4c d0 46 00 03 cc cc cc 04 cc cc cc
obj : objadr:0012FF60 vpadr:0012FF60 vtadr:0046D060 vtival(2):00401118
L5中添加的虚函数set5的入口地址被保存为第一张虚表的尾项
现象遵循继承规则。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics