快乐虾
http://blog.csdn.net/lights_joy/
本文适用于
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;
这是一个单继承的关系。
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的内容将不可更改。
那么一个vtbl究竟应该有多大?先看&parent的vtbl指针,它的值为0x00416868,看这块内存的内容:
0x00416868 c8 10 41 00 1e 10 41 00 00 00 00 00 54 76 41 00 ..A...A.....TvA.
它的两个指针0x004110c8和0x0041101e分别指向下面的函数:
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)
指向父类的两个虚函数。
再看&child的vtbl指针值为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.
这里有四个指针0x004111d1,0x00411082,0x004111b3和0x0041100a,看它们的内容:
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没有任何关系。
将上面的代码改为多重继承:
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。
vs2008下C++对象内存布局(4):多重继承(2009-9-11)
vs2008下C++对象内存布局(3):加上虚函数(2009-9-10)
vs2008下C++对象内存布局(2):简单继承(2009-9-9)
vs2008下C++对象内存布局(1)(2009-9-9)