原文链接:ordeder http://blog.csdn.net/ordeder/article/details/25477363
关于非虚函数的成员函数的调用机制,可以参考:
http://blog.csdn.net/yuanyirui/article/details/4594805
成员函数的调用涉及到面向对象语言的反射机制。
虚函数表机制可以查看下面这个blog:
http://blog.csdn.net/haoel/article/details/1948051
总结为:
其一:派生类由基类派生后,除了从基类中继承相应的基类数据成员,如果基类有虚函数,那么派生类还构建了一个指向虚函数表的指针__vfptr,该指针指向一个函数指针数组。pa ?= pb ???
实例分析
定义两个基类A和B,用C同时继承了A和B。通过分析C对象成员的内存地址分布,来分析C是如何实现A和B的继承,以及虚函数表如何维护。
#include <stdio.h> class A { public: virtual void f(){ printf("A:f\n"); } virtual void g(){ printf("A:g\n"); } virtual void f1(){ printf("A:f1\n"); } int a; }; class B { public: virtual void f(){ printf("B:f\n"); } virtual void g(){ printf("B:g\n"); } virtual void g1(){ printf("B:g1\n"); } int b; }; class C : public A, public B { public: void f(){ printf("C:f\n"); } void g(){ printf("C:g\n"); } int c; }; typedef void (*Func)(); void main() { A a; B b; C c; B *pb = &c; A *pa = &c; puts("C对象的成员内存分布初探:"); printf(" &c:\t%x\n &c.a:\t%x\n &c.b:\t%x\n &c.c:\t%x\n",&c,&c.a,&c.b,&c.c); puts("pa 与 pb"); printf("pa:\t%x\npa->a:\t%x\n",pa,&pa->a); printf("pb:\t%x\npb->b:\t%x\n",pb,&pb->b); puts("C对象完整的布局"); printf("pavtb:\t%x\npa->a:\t%x\n",pa,&pa->a); printf("pbvtb:\t%x\npa->a:\t%x\n",pb,&pb->b); printf("&c.c:\t%x\n",&c.c); puts("pavtb 和 pbvtb 内容分析:"); int *pVtb = (int *)pa; Func funp; printf("pa->vftab[0]:\t"); funp = (Func)(((int*)(*pVtb))[0]); funp(); printf("pa->vftab[1]:\t"); funp = (Func)(((int*)(*pVtb))[1]); funp(); printf("pa->vftab[2]:\t"); funp = (Func)(((int*)(*pVtb))[2]); funp(); pVtb = (int *)pb; printf("pb->vftab[0]:\t"); funp = (Func)(((int*)(*pVtb))[0]); funp(); printf("pb->vftab[1]:\t"); funp = (Func)(((int*)(*pVtb))[1]); funp(); printf("pb->vftab[2]:\t"); funp = (Func)(((int*)(*pVtb))[2]); funp(); }调试分析:
C对象在VC中的结构图:
运行结果及分析:
C对象的成员内存分布初探:
&c: 18ff24
&c.a: 18ff28
&c.b: 18ff30
&c.c: 18ff34
//1.c的首地址和c.a的首地址不是同一个地址!
//2.c.a到c.b是int类型,内存分布为4个字节,但是他们之间的内存地址差为8(18ff28 - 18ff2c - 18ff30),说以c.a和c.b之间有内容!
pa 与 pb
pa: 18ff24
pa->a: 18ff28
pb: 18ff2c
pb->b: 18ff30
//从这个结果看来,c的首地址与pa同地址,pb的首地址和上文2所说的神秘消失的内存同地址。
C对象完整的布局
pavtb: 18ff24
pa->a: 18ff28
pbvtb: 18ff2c
pa->a: 18ff30
&c.c: 18ff34
//其实:pa和pb指向的地址为虚函数表的指针所在的地址。
pavtb 和 pbvtb 内容分析: (前文blog链接已经有详细的原理说明)
pa->vftab[0]: C:f
pa->vftab[1]: C:g
pa->vftab[2]: A:f1
pb->vftab[0]: C:f
pb->vftab[1]: C:g
pb->vftab[2]: B:g1
从语句class C : public A, public B可以解释下图:
图中描述了上文例程中C的对象在内存中的分布情况。C先继承了A,后继承了B,那么C对象的内存分布先为A函数构造的虚函数表指针以及继承的A成员对象,紧接着是B函数构造的虚函数表指针以及继承的B成员对象,之后才是C的成员对象。而且这也很好的解释了同样执行如下语句后
pa = &c
pb = &c
pa和pb的地址为何是不同的这个问题。其中pa指针指向的是C对象中从A类继承过来的成员的首地址(这里将虚函数表成员也看出是一个成员看待),同理pb也是指向了B类在C中相关继承成员区域的首地址。
总而言之:
1.继承是按照类为整体进行组织的,且如果有继承虚函数,那么将有多余的一个虚函数表指针。
2.基类指针指向派生类后,同样是按照指针强制转化原则来解析派生类对象的部分区块内容(指针截断)
3.多继承中,各个基类的指针指向派生类后,各自基类指针指向的是派生类中与本身基类相关的派生类区块首地址。
面向对象--多继承&派生类对象内存布局分析&各基类指针所指向的位置分析,布布扣,bubuko.com
面向对象--多继承&派生类对象内存布局分析&各基类指针所指向的位置分析
原文:http://blog.csdn.net/ordeder/article/details/25477363