首页 > 代码库 > C++对象模型之详述C++对象的内存布局
C++对象模型之详述C++对象的内存布局
因为在C++中继承的关系比較复杂。所以本文会讨论例如以下的继承情况:
可是本文的目的是分析和验证C++对象的内存布局,而不是设计一个软件,析构函数为非virtual函数。并不会影响我们的分析和理解。因为virtual析构函数与其它的virtual函数是一样的,仅仅是做的事不一样。
所以在本文中的样例中,析构函数均不为virtual,特此说明一下。
void visitVtbl(int **vtbl, int count) { cout << vtbl << endl; cout << "\t[-1]: " << (long)vtbl[-1] << endl; typedef void (*FuncPtr)(); for (int i = 0; vtbl[i] && i < count; ++i) { cout << "\t[" << i << "]: " << vtbl[i] << " -> "; FuncPtr func = (FuncPtr)vtbl[i]; func(); } }
代码解释:
參数count指的是该虚函数表中虚函数的数量。
因为虚函数表中保存的信息并不全是虚函数的地址。也不是全部的虚函数表中都以NULL表示虚函数表中的函数地址已经到了尽头。所以为了让測试程序更好地执行。所以加上这一參数。
class Base { public: Base() { mBase1 = 101; mBase2 = 102; } virtual void func1() { cout << "Base::func1()" << endl; } virtual void func2() { cout << "Base::func2()" << endl; } private: int mBase1; int mBase2; }; class Derived : public Base { public: Derived(): Base() { mDerived1 = 1001; mDerived2 = 1002; } virtual void func2() { cout << "Derived::func2()" << endl; } virtual void func3() { cout << "Derived::func3()" << endl; } private: int mDerived1; int mDerived2; };
int main() { Derived d; char *p = (char*)&d; visitVtbl((int**)*(int**)p, 3); p += sizeof(int**); cout << *(int*)p << endl; p += sizeof(int); cout << *(int*)p << endl; p += sizeof(int); cout << *(int*)p << endl; p += sizeof(int); cout << *(int*)p << endl; return 0; }
即*(int**)p;
class Base1 { public: Base1() { mBase1 = 101; } virtual void funcA() { cout << "Base1::funcA()" << endl; } virtual void funcB() { cout << "Base1::funcB()" << endl; } private: int mBase1; }; class Base2 { public: Base2() { mBase2 = 102; } virtual void funcA() { cout << "Base2::funcA()" << endl; } virtual void funcC() { cout << "Base2::funcC()" << endl; } private: int mBase2; }; class Derived : public Base1, public Base2 { public: Derived(): Base1(), Base2() { mDerived = 1001; } virtual void funcD() { cout << "Derived::funcD()" << endl; } virtual void funcA() { cout << "Derived::funcA()" << endl; } private: int mDerived; };
int main() { Derived d; char *p = (char*)&d; visitVtbl((int**)*(int**)p, 3); p += sizeof(int**); cout << *(int*)p << endl; p += sizeof(int); visitVtbl((int**)*(int**)p, 3); p += sizeof(int**); cout << *(int*)p << endl; p += sizeof(int); cout << *(int*)p << endl; return 0; }
当中一个为主要实例。它与第一个基类(如本例中的Base1)共享。其它的为次要实例,与其它基类(如本例中的Base2)有关。
如本例中,子类新声明的与Base1共享的虚函数表中。
这样做的目的是为了解决不同的父类类型的指针指向同一个子类实例。而能够调用到实际的函数。
class Base { public: Base() { mBase = 11; } virtual void funcA() { cout << "Base::funcA()" << endl; } virtual void funcX() { cout << "Base::funcX()" << endl; } protected: int mBase; }; class Base1 : public Base { public: Base1(): Base() { mBase1 = 101; } virtual void funcA() { cout << "Base1::funcA()" << endl; } virtual void funcB() { cout << "Base1::funcB()" << endl; } private: int mBase1; }; class Base2 : public Base { public: Base2(): Base() { mBase2 = 102; } virtual void funcA() { cout << "Base2::funcA()" << endl; } virtual void funcC() { cout << "Base2::funcC()" << endl; } private: int mBase2; }; class Derived : public Base1, public Base2 { public: Derived(): Base1(), Base2() { mDerived = 1001; } virtual void funcD() { cout << "Derived::funcD()" << endl; } virtual void funcA() { cout << "Derived::funcA()" << endl; } private: int mDerived; };
int main() { Derived d; char *p = (char*)&d; visitVtbl((int**)*(int**)p, 4); p += sizeof(int**); cout << *(int*)p << endl; p += sizeof(int); cout << *(int*)p << endl; p += sizeof(int); visitVtbl((int**)*(int**)p, 3); p += sizeof(int**); cout << *(int*)p << endl; p += sizeof(int); cout << *(int*)p << endl; p += sizeof(int); cout << *(int*)p << endl; return 0; }
因为在该对象中有两处的变量的名字都叫mBase。所以编译器不能推断到底该使用哪一个成员变量。
所以在訪问Base中的成员时。须要加上域作用符来明白说明是哪一个子类的成员。如:
class Base { ...... }; class Base1 : virtual public Base { ...... };
int main() { Base1 b1; char *p = (char*)&b1; visitVtbl((int**)*(int**)p, 3); p += sizeof(int**); cout << *(int*)p << endl; p += sizeof(int); visitVtbl((int**)*(int**)p, 3); p += sizeof(int**); cout << *(int*)p << endl; return 0; }
表现为以下的方面:
在普通的单一继承中。基类的成员位于派生类的成员之前。
而在单一虚继承中,首先是其普通基类的成员,接着是派生类的成员,最后是虚基类的成员。
而在单一虚继承中,派生类的虚函数表有n个(n为虚基类的个数)额外的虚数函数表,即总有n+1个虚函数表。
class Base { ...... }; class Base1 : virtual public Base { ...... }; class Base2 : virtual public Base { ...... }; class Derived : public Base1, public Base2 { ...... };
int main() { Derived d; char *p = (char*)&d; visitVtbl((int**)*(int**)p, 3); p += sizeof(int**); cout << *(int*)p << endl; p += sizeof(int); visitVtbl((int**)*(int**)p, 2); p += sizeof(int**); cout << *(int*)p << endl; p += sizeof(int); cout << *(int*)p << endl; p += sizeof(int); visitVtbl((int**)*(int**)p, 2); p += sizeof(int**); cout << *(int*)p << endl; return 0; }
int main() { Derived d; Base *basePtr = &d; Base1 *base1Ptr = &d; Base2 *base2Ptr = &d; Derived *derivedPtr = &d; cout << typeid(*basePtr).name() << endl; cout << typeid(*base1Ptr).name() << endl; cout << typeid(*base2Ptr).name() << endl; cout << typeid(*derivedPtr).name() << endl; return 0; }
当使用基类的指针指向一个派生类的对象时,编译器会安插相应的代码。调整指针的指向,使基类的指针指向派生类对象中其相应的基类子对象的起始处。
所以不管p指向的是Base的对象。还是Base的派生类的对象,其virtual函数vfunc在虚函数表中的索引是不变的(均为1)。
编译器可能会把virtual函数调用的代码改动为例如以下的伪代码:
若p指向的是一个Base的派生类的对象。则调用Base的派生类对象的Base子对象的虚函数表中的索引值为1的函数。这样便实现了多态 。这样的函数调用是依据指针p所指的对象的虚函数表来实现的。在编译时因为无法确定指针p所指的真实对象。所以无法确定真实要调用哪一个函数,仅仅有在执行时依据指针p所指的对象来动态决定。所以说,虚函数是在执行时动态绑定的,而不是在编译时静态绑定的。
C++对象模型之详述C++对象的内存布局