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类的实例对象obj1和obj2,打印它们的信息如下:
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
每个类维持自己持有的一张虚表,因此C011和C012的虚表入口地址是不一样的。
但是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
如果声明C011和C013的两个实例对象,打印它们的信息如下:
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的入口地址被保存为第一张虚表的尾项。
现象遵循继承规则。
分享到:
相关推荐
涉及各种情况下C++对象的sizeof大小,包括单一类对象,继承,重复继承 多继承 单一虚继承 等各种情况下的对象大小。对C++对象内存布局有清楚了解。
C++对象内存布局[归类].pdf
介绍C++对象在内存中是怎样分布的,有助于深层学习C++。
详细理解vs2008C++编程内存使用情况!!值得一看...
c++ 标准不规定 c++ 实现的时候的对象的具体的内存布局,除了在某些方面有小的限制以外,c++ 对象在内存里面的布局完全是由编译器自行决定,这里只是讨论 vc++ .net 2003 build 7.1.3091 的实现方式.
C++ 对象的内存布局。全面分析C++ 对象的内存布局。
安卓逆向学习笔记之ART中的C++对象内存布局及获取art-method和dex-file对象.docx
C++ 对象的内存布局(下)1
1)有成员变量的情况。 2)有重复继承的情况。 3)有虚拟继承的情况。 4)有钻石型虚拟继承的情况。
c++对象的内存布局 对c++做了非常通俗 而且经典的分析 如果你想对c++的工作方式有更深入的了解 这是一份非常有帮助的文档
C++对象模型在内存中的实现,讲述了类,继承以及虚继承的内存布局;成员变量和成员函数的访问已经访问时的开销情况,包含虚函数的情况,考察构造函数,析构函数,以及特殊的赋值操作符成员函数是如何工作的,数组是...
C++对象的内存布局[归纳].pdf
这是一些关于基类含有virtual函数或子类是virtual继承的对象的内存布局。其中有我截的一些图、内存布局图、文字说明,不过能力有限,说的不是很清楚,望谅解
分析c++对象在内存中的布局情况.pdf
网上看到的一篇博客,对C++对象内存模式讲的详细易懂。
-) 对象的影响因素 简而言之,我们一个类可能会有如下的影响因素: 1)成员变量2)虚函数(产生虚函数表)3)单一继承(只继承于一个类)4)多重继承(继承多个类
c++继承中的内存布局 VC++对象模型
讲解 C++ 对象内存布局的非常好的一本书,可以从最底层,最本质的地方认识 C++ 的类,从而掌握面向对象技术的精髓 。
详细介绍C++ 对象模型, 对象内存布局