很早就知道虚函数采用一种叫虚函数表的机制,在类的内存空间中添加一个"隐藏"的成员变量的方法保存了虚函数表的指针。自己便对单继承的虚函数的执行原理有了些了解,但一直不知多继承怎么实现,今天就研究了一下。
class Base;
class BaseA;
class BaseB:public virtual Base
class BaseC:public virtual Base
class Derived:public BaseA,public BaseB,public BaseC
反汇编发现Derived的内存分配如下:
*原来没有vTable的父类就算在继承列表里先声明也不一定放在最前,可能主要考虑这样vTable统一放最前操作比较方便.
当以Base*调用时:
访问vFunBase,直接通过前四个字节的vTable(Base)
访问m_base直接根据偏移量访问
当以BaseA*调用时:
直接访问
当以BaseB*调用时:
访问Base*的vFunBase和m_base通过(this+4)来得到描述Base类位置偏移的说明,从说明中的到与偏移量有关的值(0x04处)
访问vFunB1,直接通过前四个字节的vTable(Base)
当以BaseC*调用时:
与BaseB相似(此时指针只指向BaseC的变量,在Derived的指针继承上偏移0x0c)
当以Derived*调用时:
访问vFunBase与BaseB相同(因为指向相同)
访问vFunB1与BaseB相同(因为指向相同)
访问vFunC1和vFunC2时,this+0x0c得到BaseC的vTable,然后存取里面的函数。
原来没想到会用多个vTable,今天了解了,呵呵
其实细想一个vtable和几个没有什么关系,编译器就知道每一个类型的内存布局,而调用函数的机器指令是编译时确定的,这时编译器已经知道指针类型(不一定是对象真实类型),那就可以知道需要的函数在这个类型的哪个vtable的第几个位置,生成固定代码即可。
只要你类型转换时指针指向正确(参见第二幅图,转化为不同父类型后指针是不一样的),那么编译器就会根据指针指向的类型的内存布局找到你需要的函数.
*好象调用父类的非虚函数时,传给函数的this指针也如上图所示(想想也应该这样,因为父类的函数只会按父类的内存布局操作,有空会验证一下)
自己发现了vTable-4处有猫腻,但就是搞不明白是什么,通过http://bbs.pediy.com/showthread.php?t=61873了解到一些东西(见最后所附)
由于多态和RTTI的目的,对象内部隐藏的那些指针指向的值应该都是和当前对象的真实类型相关,因此相同类的不同对象中隐藏指针的值应该相同.(不要被我图中括号括住的"BaseB"等迷惑,那只是为了说明他们和哪个父类相关)
事实三处RTTITypeDescriptor指针是一样的,在dynamic_cast < type-id > ( expression ) 里会直接把RTTITypeDescriptor的地址作为参数传入.但三处RTTICompleteObjectLocator指针却不一样,现在也没查到RTTICompleteObjectLocator的前12个字节是什么.
附:
RTTI(RuniTime TypeInfo)是c++的一种运行时类型识别机制
当一个类的引用或者指针的值去进行“类型识别", 叫做动态识别
当一个类的实例去进行"类型识别", 叫做静态识别
动态识别需要编译打开了/GR开关才可有效, 否则会引起一个运行时错误, 而静态的不需要
对于大型工程而言, 静态识别并没有多少意义
实现原理:
这里假设你对类的虚表非常熟悉, 如果一个类存在虚函数, 或者他的基类存在虚函数,
或者virtual继承了其他类,或者开启了RTTI, 那么编译器会为该类生成一个虚表,
该表是一个该类需要虚实现的函数的指针表,一般类的头4个字节为该表的指针
(当基类是多重继承的时候, 非第一个继承的基类的虚表不是在头4字节)
当有RTTI在的时候, 虚表-4的位置是一个叫做RTTI Complete Object Locator的描述体
该描述体+c的位置是一个叫做RTTI Type Descriptor的描述体, 该描述体+8的位置是一段
原始类名的buf, 我们只要得到了该buf, 就能得到类的名字.
RTTI Type Descriptor这个描述体在c++中叫做type_info, 他的定义如下
class type_info {
public:
_CRTIMP virtual ~type_info();
_CRTIMP int operator==(const type_info& rhs) const;
_CRTIMP int operator!=(const type_info& rhs) const;
_CRTIMP int before(const type_info& rhs) const;
_CRTIMP const char* name() const;
_CRTIMP const char* raw_name() const;
private:
void *_m_data;
char _m_d_name[1];
type_info(const type_info& rhs);
type_info& operator=(const type_info& rhs);
};
typeid作为一个c++关键字, 给它传递一个任意类型的实例, 他能够返回一个type_info的引用。