C++使用继承时子对象的内存布局
1 示例程序
class A { protected: int a; public: A() : a(1) {} virtual void a1() {} virtual void a2() {} }; class B { protected: int b; public: B() : b(2) {} virtual void b1() {} virtual void b2() {} }; class C : public A, public B { protected: int c; public: C() : c(3) {} virtual void a1() {} virtual void b1() {} }; int _tmain(int argc, _TCHAR* argv[]) { C *c = new C; B *b = (B *)c; return 0; }
类 A/B/C 之间的关系如下:
2 对象的内存布局
C *c = new C();
在上面这段代码中,对象 c 的内存布局如下:
低地址 | ||||
… | ||||
1. 虚表指针 | –> | &C::a1 | &A::a2 | . |
2. A::a | ||||
3. 虚表指针 | –> | &C::b1 | &B::b2 | . |
4. B::b | ||||
5. c::c | ||||
… | ||||
高地址 |
c 对象从低地址到高地址依次存储了:A 的虚表指针、A 的成员变量、B 的虚表指针、B 的成员变量、C 的成员变量。我们发现,虚函数表 A 中的 A::a1 函数被 C::a1 函数覆盖了,虚函数表 B 中 B::b1 函数被 C::b1 覆盖了,这是因为 C 重写了 a1 和 b1方法。这意味着通过 c 对象中的虚表指针调用 a1 或 b1 函数,只能够调用到 C 类中重写的方法。
C *c = new C; c->b1(); B *b = c; b->b1();
上面的两句调用都会调用 C 的 b1 方法。另外,在对 b 赋值时,b 得到的结果并不是 c 的值,而是 c+8,这正好是 c 对象中用于存储 B 虚表指针的地址。