vs2008下C++对象内存布局(3):加上虚函数

快乐虾

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

[email protected]

 

本文适用于

Xp sp3

Vs2008

 

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

 

这次我们为父类加上虚函数:

class CParent

{

public:

     int parent_a;

     int parent_b;

 

public:

     virtual void parent_f1()

     {

         parent_a = 0x10;

     }

     virtual void parent_f2()

     {

         parent_b = 0x20;

     }

};

 

class CChild : public CParent

{

public:

     int child_a;

     int child_b;

 

public:

     virtual void parent_f1()

     {

         child_a = 0x30;

     }

     virtual void parent_f2()

     {

         child_b = 0x40;

     }

};

CChild child, *pchild;

CParent parent, *pparent;

 

1.1.1   内存布局

同样,我们先对其进行简单赋值再观察内存变化。

先对父类进行赋值:

     parent.parent_a = 1;

004139FE C7 05 B8 81 41 00 01 00 00 00 mov         dword ptr [parent+4 (4181B8h)],1

     parent.parent_b = 2;

00413A08 C7 05 BC 81 41 00 02 00 00 00 mov         dword ptr [parent+8 (4181BCh)],2

看内存:

0x004181B4  5c 68 41 00 01 00 00 00 02 00 00 00 00 00 00 00  /hA.............

很显然,在数据之前插入了四个字节,猜想这个应该就是所谓的vtbl的指针,如果这样,那么所有同类对象都应该有相同的指针,定义两个变量:

CParent v1, v2;

先看v1的内存:

0x0012FF58  5c 68 41 00 cc cc cc cc cc cc cc cc cc cc cc cc  PhA.............

再看v2的内存:

0x0012FF44  5c 68 41 00 cc cc cc cc cc cc cc cc cc cc cc cc  PhA.............

果然是一样的,嘿。

再看子类的赋值:

          child.child_a = 3;

00413A12 C7 05 AC 81 41 00 03 00 00 00 mov         dword ptr [child+0Ch (4181ACh)],3

     child.child_b = 4;

00413A1C C7 05 B0 81 41 00 04 00 00 00 mov         dword ptr [child+10h (4181B0h)],4

&child的内存:

0x004181A0  50 68 41 00 00 00 00 00 00 00 00 00 03 00 00 00  PhA.............

0x004181B0  04 00 00 00 5c 68 41 00 01 00 00 00 02 00 00 00  ..../hA.........

很明显,父类和子类指向的vtbl是不一样的。

 

1.1.2   父类虚函数调用

先看父类虚函数的调用:

     pparent = &parent;

00413A26 C7 05 98 81 41 00 B4 81 41 00 mov         dword ptr [pparent (418198h)],offset parent (4181B4h)

     pparent->parent_f1();

00413A30 A1 98 81 41 00   mov         eax,dword ptr [pparent (418198h)]

00413A35 8B 10            mov         edx,dword ptr [eax]

00413A37 8B F4            mov         esi,esp

00413A39 8B 0D 98 81 41 00 mov         ecx,dword ptr [pparent (418198h)]

00413A3F 8B 02            mov         eax,dword ptr [edx]

00413A41 FF D0            call        eax 

00413A43 3B F4            cmp         esi,esp

00413A45 E8 1E D7 FF FF   call        @ILT+355(__RTC_CheckEsp) (411168h)

     pparent->parent_f2();

00413A4A A1 98 81 41 00   mov         eax,dword ptr [pparent (418198h)]

00413A4F 8B 10            mov         edx,dword ptr [eax]

00413A51 8B F4            mov         esi,esp

00413A53 8B 0D 98 81 41 00 mov         ecx,dword ptr [pparent (418198h)]

00413A59 8B 42 04         mov         eax,dword ptr [edx+4]

00413A5C FF D0            call        eax 

00413A5E 3B F4            cmp         esi,esp

00413A60 E8 03 D7 FF FF   call        @ILT+355(__RTC_CheckEsp) (411168h)

这回的函数调用就不是简单地使用名称进行调用了,而是先取出vtbl的首地址(pparent指向的头四个字节),然后加上偏移量,再取出这个新地址中存储的指针,最后用call进行函数调用。这个偏移量是由编译器自动排列得到的。

我们看看vtbl的内容:

0x0041685C  b4 10 41 00 08 12 41 00 44 76 41 00 dc 10 41 00  ..A...A.DvA...A.

这里有两个指针:0x004110b40x00411208,看看它们指向的位置:

004110B4 E9 57 0C 00 00   jmp         CParent::parent_f1 (411D10h)

……..

00411208 E9 43 0B 00 00   jmp         CParent::parent_f2 (411D50h)

果然指向的是父类的成员函数。

1.1.3   子类虚函数的调用

现在我们用子类的指针调用子类的虚函数:

     pchild = &child;

00413A65 C7 05 9C 81 41 00 A0 81 41 00 mov         dword ptr [pchild (41819Ch)],offset child (4181A0h)

     pchild->parent_f1();

00413A6F A1 9C 81 41 00   mov         eax,dword ptr [pchild (41819Ch)]

00413A74 8B 10            mov         edx,dword ptr [eax]

00413A76 8B F4            mov         esi,esp

00413A78 8B 0D 9C 81 41 00 mov         ecx,dword ptr [pchild (41819Ch)]

00413A7E 8B 02            mov         eax,dword ptr [edx]

00413A80 FF D0            call        eax 

00413A82 3B F4            cmp         esi,esp

00413A84 E8 DF D6 FF FF   call        @ILT+355(__RTC_CheckEsp) (411168h)

     pchild->parent_f2();

00413A89 A1 9C 81 41 00   mov         eax,dword ptr [pchild (41819Ch)]

00413A8E 8B 10            mov         edx,dword ptr [eax]

00413A90 8B F4            mov         esi,esp

00413A92 8B 0D 9C 81 41 00 mov         ecx,dword ptr [pchild (41819Ch)]

00413A98 8B 42 04         mov         eax,dword ptr [edx+4]

00413A9B FF D0            call        eax 

00413A9D 3B F4            cmp         esi,esp

00413A9F E8 C4 D6 FF FF   call        @ILT+355(__RTC_CheckEsp) (411168h)

它的调用方式和父类完全一样,都是先取vtbl的首地址,再加上偏移量后进行call。看一下子类的vtbl

0x00416850  a9 11 41 00 0d 12 41 00 20 76 41 00 b4 10 41 00  ..A...A. vA...A.

这里有两个指针:0x004111a90x0041120d,看看它们指向的位置:

004111A9 E9 82 03 00 00   jmp         CChild::parent_f1 (411530h)

……..

0041120D E9 6E 03 00 00   jmp         CChild::parent_f2 (411580h)

果然指向的是子类的成员函数。

1.1.4   父类指针调用子类的虚函数

这回我们用一个父类指针来调用虚函数:

     pparent = &child;

00413AA4 C7 05 98 81 41 00 A0 81 41 00 mov         dword ptr [pparent (418198h)],offset child (4181A0h)

     pparent->parent_f1();

00413AAE A1 98 81 41 00   mov         eax,dword ptr [pparent (418198h)]

00413AB3 8B 10            mov         edx,dword ptr [eax]

00413AB5 8B F4            mov         esi,esp

00413AB7 8B 0D 98 81 41 00 mov         ecx,dword ptr [pparent (418198h)]

00413ABD 8B 02            mov         eax,dword ptr [edx]

00413ABF FF D0            call        eax 

00413AC1 3B F4            cmp         esi,esp

00413AC3 E8 A0 D6 FF FF   call        @ILT+355(__RTC_CheckEsp) (411168h)

可以看到,这个时候使用的是子函数的VTBL,因此调用的自然是子类的虚函数。

 

 

 

 

 

2       参考资料

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

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

 

 

 

 

 

 

你可能感兴趣的:(vs2008下C++对象内存布局(3):加上虚函数)