vs2008下C++对象内存布局(5):vtbl

快乐虾

http://blog.csdn.net/lights_joy/

lights@hb165.com

 

本文适用于

Xp sp3

Vs2008

 

欢迎转载,但请保留作者信息

 

再看看vtbl的其它内容,先写段代码:

class CParent

{

public:

     int parent_a;

     int parent_b;

 

public: 

     virtual void parent_f1() {}

     virtual void parent_f2() {}

 

public: 

     void parent_f3() {}

     void parent_f4() {}

};

 

class CChild : public CParent

{

public:

     int child_a;

     int child_b;

 

public:       // 子类的函数

     virtual void child_f1() {}

     virtual void child_f2() {}

 

public:  // 不可重载的函数

     void child_f3() {}

     void child_f4() {}

 

public:       // 重载父类的函数

     virtual void parent_f2() {}

     virtual void parent_f1() {}

};

CChild child, *pchild;

CParent parent, *pparent;

这是一个单继承的关系。

1.1.1   Vtbl的属性

Vtbl应该是在链接时确定,之后就不再改变,那么它应该具有只读的属性,先对此做个验证:

     MEMORY_BASIC_INFORMATION mi;

     ::VirtualQueryEx(::GetCurrentProcess(), (void*)(*(unsigned int*)&parent), &mi, sizeof(mi));

这段代码将查询vtbl所在内存块的属性,返回的mi值如下:

              BaseAddress   0x00416000 ___xc_a     void *

              AllocationBase 0x00400000    void *

              AllocationProtect    0x00000080    unsigned long

              RegionSize      0x00002000    unsigned long

              State       0x00001000    unsigned long

              Protect    0x00000002    unsigned long

              Type       0x01000000    unsigned long

Protect这个成员的值表明vtbl所在的内存块具有PAGE_READONLY的属性,MSDN这样解释这个属性的意义:

Enables read access to the committed region of pages. An attempt to write to the committed region results in an access violation. If the system differentiates between read-only access and execute access, an attempt to execute code in the committed region results in an access violation.

因而vtbl的内容将不可更改。

1.1.2   单继承时vtbl的内容

那么一个vtbl究竟应该有多大?先看&parentvtbl指针,它的值为0x00416868,看这块内存的内容:

0x00416868  c8 10 41 00 1e 10 41 00 00 00 00 00 54 76 41 00  ..A...A.....TvA.

它的两个指针0x004110c80x0041101e分别指向下面的函数:

CParent::parent_f1:

004110C8 E9 33 08 00 00   jmp         CParent::parent_f1 (411900h)

CParent::parent_f2:

0041101E E9 1D 09 00 00   jmp         CParent::parent_f2 (411940h)

指向父类的两个虚函数。

再看&childvtbl指针值为0x00416850,看它的内容:

0x00416850  d1 11 41 00 82 10 41 00 b3 11 41 00 0a 10 41 00  ..A...A...A...A.

0x00416860  00 00 00 00 3c 76 41 00 c8 10 41 00 1e 10 41 00  ....<vA...A...A.

这里有四个指针0x004111d10x004110820x004111b30x0041100a,看它们的内容:

CChild::parent_f1:

004111D1 E9 9A 06 00 00   jmp         CChild::parent_f1 (411870h)

CChild::parent_f2:

00411082 E9 A9 07 00 00   jmp         CChild::parent_f2 (411830h)

CChild::child_f1:

004111B3 E9 F8 05 00 00   jmp         CChild::child_f1 (4117B0h)

CChild::child_f2:

0041100A E9 E1 07 00 00   jmp         CChild::child_f2 (4117F0h)

这说明子类的VTBL前面的部分是和父类的VTBL一致的,只是它们指向的内容不一样而已。想想也是,如果我们把子类的指针赋给父类的指针再调用虚函数,此时编译器将按照父类中虚函数的偏移量来进行函数调用,如果子类的VTBL中的函数指针意义和父类不一致,那么调用自然失败。

因而可以得出结论,一个类的VTBL的大小等于父类的VTBL大小加上这个类中定义的虚函数个数。而非虚函数则与VTBL没有任何关系。

1.1.3   多重继承时VTBL的内容

将上面的代码改为多重继承:

class CParentA

{

public:

     int parenta_a;

     int parenta_b;

 

public: 

     virtual void parenta_f1() {}

     virtual void parenta_f2() {}

 

public: 

     void parenta_f3() {}

     void parenta_f4() {}

};

 

class CParentB

{

public:

     int parentb_a;

     int parentb_b;

 

public: 

     virtual void parentb_f1() {}

     virtual void parentb_f2() {}

 

public: 

     void parentb_f3() {}

     void parentb_f4() {}

};

 

class CChild : public CParentA, public CParentB

{

public:

     int child_a;

     int child_b;

 

public:       // 子类的函数

     virtual void child_f1() {}

     virtual void child_f2() {}

 

public:  // 不可重载的函数

     void child_f3() {}

     void child_f4() {}

 

public:       // 重载父类A的函数

     virtual void parenta_f2() {}

     virtual void parenta_f1() {}

 

public:       // 重载父类B的函数

     virtual void parentb_f2() {}

     virtual void parentb_f1() {}

};

CChild child, *pchild;

我们只看子类的VTBL,从前面的文章可以知道,子类将有两个VTBL,其第一个VTBL指针值为0x0041685c,看它的内容:

0x0041685C  49 12 41 00 44 12 41 00 b3 11 41 00 0a 10 41 00  I.A.D.A...A...A.

0x0041686C  00 00 00 00 00 00 00 00 54 76 41 00 f0 10 41 00  ........TvA...A.

它有四个指针:

00411249 E9 B2 06 00 00   jmp         CChild::parenta_f1 (411900h)

00411244 E9 67 06 00 00   jmp         CChild::parenta_f2 (4118B0h)

004111B3 E9 78 06 00 00   jmp         CChild::child_f1 (411830h)

0041100A E9 61 08 00 00   jmp         CChild::child_f2 (411870h)

和单继承时一样。

再看第二个VTBL的内容,其指针为0x00416850

0x00416850  3a 12 41 00 35 12 41 00 a0 75 41 00 49 12 41 00  :.A.5.A..uA.I.A.

它有两个指针:

0041123A E9 41 05 00 00   jmp         CChild::parentb_f1 (411780h)

00411235 E9 06 07 00 00   jmp         CChild::parentb_f2 (411940h)

紧接着这两个指针之后就是第一个VTBL的内容。在这二者之间还有一个指针0x004175a0,猜想和type info有关,因为将编译选项里的“生成运行时类型信息”关闭后这个指针将为NULL

 

 

 

 

 

2       参考资料

vs2008C++对象内存布局(4):多重继承(2009-9-11)

vs2008C++对象内存布局(3):加上虚函数(2009-9-10)

vs2008C++对象内存布局(2):简单继承(2009-9-9)

vs2008C++对象内存布局(1(2009-9-9)

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌云阁主

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值