回顾一下以前对虚函数表及虚表指针的概念:
1. 虚函数表属于类,同类对象间共享该虚函数表(貌似虚函数表里面维护了一个函数地址的指针数组)。
2.不同对象各自维护一个虚表指针指向类的虚表,类对象大小包含成员变量大小(含虚表指针vptr大小) ——(计算大小要考虑字节对齐 32位默认4字节对齐,64位8字节)。
3.虚表指针在对象内存的开始位置。
4.虚函数调用时,动态联编的多态特性及运行时判断执行哪个函数,所以虚函数执行效率比普通函数要低。
5.子类继承父类后,如果子类有父类的重名虚函数,则会覆盖(重写)父类的虚函数,虚表内函数指针也会替换成子类的。
class Base{
virtual void print(){}
};
class Base2{
virtual void dprint(){}
};
class CBase: public Base, public Base2{
};
//g++ align.cpp -fdump-class-hierarchy
-------------------------------------linux 64位系统------------------------------------------------
Vtable for Base //虚函数表内存储了虚函数的地址
Base::_ZTV4Base: 3u entries //虚表是属于类,而不是属于某个具体的对象,一个类只需要一个虚表
0 (int (*)(...))0 //一个空的变长参数函数指针??
8 (int (*)(...))(& _ZTI4Base) //一个指向虚函数表的指针 = 把虚函数表地址转成变长参数的函数指针?
16 Base::print//24 xxx
Class Base
size=8 align=8
base size=8 base align=8
Base (0x7f1f07d2bd90) 0 nearly-empty
vptr=((& Base::_ZTV4Base) + 16u) //可以看出虚函数表首地址还有两个函数指针,偏移16字节后是第一个虚函数的地址//虚函数表指针vptr指向的是第一个虚函数,而不是虚函数表首地址
//同理,32位系统指针4字节,则应该是虚函数表指针=虚函数表首地址 + 8字节偏移 = 第一个虚函数地址
Vtable for Base2
Base2::_ZTV5Base2: 3u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5Base2)
16 Base2::dprintClass Base2
size=8 align=8
base size=8 base align=8
Base2 (0x7f1f07d2bf50) 0 nearly-empty //每个类的vptr放在类内存空间的起始位置
vptr=((& Base2::_ZTV5Base2) + 16u)
Vtable for CBase
CBase::_ZTV5CBase: 6u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5CBase)
16 Base::print
24 (int (*)(...))-0x00000000000000008
32 (int (*)(...))(& _ZTI5CBase)
40 Base2::dprintClass CBase //C++多重继承中,会顺序存储所有基类的虚函数表指针(即有多个虚表指针)
size=16 align=8base size=16 base align=8
CBase (0x7f1f07d91380) 0
vptr=((& CBase::_ZTV5CBase) + 16u) //子类复用第一个基类的虚表指针,并对虚表进行覆盖或者添加
Base (0x7f1f07d96070) 0 nearly-empty
primary-for CBase (0x7f1f07d91380) //第一个基类会用于初始化子类(我认为是初始化子类虚表),然后子类对象的虚表指针指向这个子类虚表。
Base2 (0x7f1f07d960e0) 8 nearly-empty
vptr=((& CBase::_ZTV5CBase) + 40u) //子类对象第二个虚表指针,指向子类虚表中来自第二个基类的第一个虚函数地址
___________________________________________________________
class Base{
virtual void print(){}
};class CBase: public Base{
};
Vtable for Base
Base::_ZTV4Base: 3u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI4Base)
16 Base::printClass Base
size=8 align=8
base size=8 base align=8
Base (0x7f1eeb8e9d90) 0 nearly-empty
vptr=((& Base::_ZTV4Base) + 16u)Vtable for CBase
CBase::_ZTV5CBase: 3u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5CBase)
16 Base::printClass CBase
size=8 align=8
base size=8 base align=8
CBase (0x7f1eeb954070) 0 nearly-empty
vptr=((& CBase::_ZTV5CBase) + 16u)
Base (0x7f1eeb9540e0) 0 nearly-empty
primary-for CBase (0x7f1eeb954070)
Class Base2
size=8 align=8
base size=8 base align=8
Base2 (0x7f16e5f4ea10) 0 nearly-empty
vptr=((& Base2::_ZTV5Base2) + 16u)
Vtable for CBase
CBase::_ZTV5CBase: 5u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5CBase)
16 Base::print
24 Base::test
32 CBase::ok //子类虚函数地址会放在基类后面
某大牛如是说:子类中声明的虚函数除了覆盖各个基类对应函数的指针外,还额外添加一份到第一个基类的vptr中(体现了共用的意义)。我表示对共用这个词持保留意见,第一虚表属于各个类,父类和子类不是同一个;第二虚表指针属于对象,各个对象都有。子类复用继承下来的父类声明的虚表指针而已,该指针指向的是子类虚表。