在C++对象模型中,Nonstatic data members 被配置于每一个 class object 之内,static data members 则被存放在个别 class object 之外。Static 和nonstatic function members 也被放在个别的class object 之外。存在 virtual function 时,每个class 会产生出一堆指向 virtual function 的指针,这些指针存放在一个表中,称之为虚函数表 virtual table。其实质就是一个函数指针数组,里面存放的是虚函数指针。每一个 class object 被安插一个虚表指针 vptr,指向相关的 virtual table,虚表指针通常置于 class object 的第一位置。另外,当子类虚继承父类时,子类对象中还会安插虚基类指针。注意此处 class 和 class object 的区别。如此说来一个类实例当中存放的就是 nonstatic data member,虚表指针(存在虚函数时或父类中含有虚函数),虚基类指针(虚拟继承时)。类继承时体积会变大,但在虚拟继承的情况下,基类永远只存在一个实例。
每一个 static data member 只有一个实例,存放在程序的 data segment 之中,视作是全局变量,static成员独立于任何对象而存在,不是类类型对象的组成部分,(非const类型)必须在类定义体的外部定义,不同于普通数据成员,static成员不是通过类构造函数进行初始化。
1 | class Base //测试 |
类中 static成员在编译时并不分配内存,把它当成全局函数在类外进行初始化时才分配内存,在类外初始化时记得标上数据类型,这样编译器才知道应该为该 static成员分配多大内存。
当类中申明或定义虚函数时,该类会产生出一堆指向虚函数的指针,放在虚函数表中,该类中有多少个虚函数,那个对应的虚函数指针一次存放在该虚函数表中,当类被实例化时,每个实例 class object 会被安插一个虚函数表指针,一般是在 class object 的第一个位置(visual studio C++ 环境),指向相关虚函数表。
换言之,每个含有虚函数的类有一张虚函数表vtbl,其中每一项是一个虚函数地址,指向虚表的指针 vptr 是被安插在类实例 class object 中,并且它指向对象所属的类的虚表,这样的话即使类中申明了多个虚函数,那么class object 中只会占用一个指针大小的空间。
1 | class Base //测试 |
那么虚函数表究竟存放在哪呢?看上面程序的测试结果
可以看出,虚函数表的地址为:0x0089cc64,常量字符串的地址为:0x0089cc70.所以在visual studio C++中,虚函数表示存放在常量段,不同的编译器可能会有区别。
所以虚函数表是属于一个类所有对象的,不是某个对象特有的,是一个类所有对象共有的。
如果一个子类虚继承父类,即虚继承时,需要一个虚基类表来记录需继承关系,这样子类就会多一个虚基类表指针,不管多重还是单一继承,子类虚基类表指针只有一个。
1 | class Base |
子类对象 d 中由于申明了一个与父类 Base 不同名的虚函数,所以在虚继承时会额外安插一个虚表指针,虚继承时会出现子类中持有多个虚函数表的情况,非虚继承时则不会。如果改为非虚继承,那么该类中就只会持有一个虚函数表,且没有虚基类指针,则 sizeof(d) = 12。
class DDriver 中如果再虚继承 Base,其实例对象的大小不变,也就是说虚继承情况下,基类只存在一个实例。
这样,一个类的对象的内存大小包括:
- 所有非静态数据成员的大小
- 由内存对齐而填补的内存大小
- 为了支持 virtual,由内部产生的额外负担
在VC中数据成员的布局为 [C++对象模型很大部分取决于编译器] :
- vptr 部分(如果基类有,则继承基类的)
- vbptr (虚继承)
- 基类成员 (如果继承)
- 自身数据成员
- 虚基类数据成员 (按声明顺序)