能力值:
( LV3,RANK:20 )
|
-
-
2 楼
这是我以前看这里时自己分析的
class A {
public:
__declspec(noinline) A() { printf("ctor in A\n"); }
__declspec(noinline) virtual void A1() { printf("A1 in A\n"); }
__declspec(noinline) virtual void A2() { printf("A2 in A\n"); }
__declspec(noinline) virtual ~A() { printf("dtor in A\n"); }
};
class B : virtual public A {
public:
__declspec(noinline) B() { printf("ctor in B\n"); }
__declspec(noinline) virtual void B1() { printf("B1 in B\n"); }
__declspec(noinline) virtual ~B() { printf("dtor in B\n"); }
};
class C : virtual public A {
public:
__declspec(noinline) C() { printf("ctor in C\n"); }
__declspec(noinline) virtual void C1() { printf("C1 in C\n"); }
__declspec(noinline) ~C() { printf("dtor in C\n"); }
};
class X {
public:
__declspec(noinline) X() { printf("ctor in X\n"); }
__declspec(noinline) virtual void X1() { printf("X1 in X\n"); }
__declspec(noinline) virtual ~X() { printf("dtor in X\n"); }
};
class Y : virtual public X {
public:
__declspec(noinline) Y() { printf("ctor in Y\n"); }
__declspec(noinline) virtual void Y1() { printf("Y1 in Y\n"); }
__declspec(noinline) virtual ~Y() { printf("dtor in Y\n"); }
};
class Z : virtual public X {
public:
__declspec(noinline) Z() { printf("ctor in Z\n"); }
013019D0 push ebp
013019D1 mov ebp,esp
013019D3 push ecx
013019D4 push esi
013019D5 mov esi,ecx
013019D7 push 13032F4h
013019DC mov eax,dword ptr [esi+4] ; 此时虚基类表指针已经被子类设置好了
013019DF mov dword ptr [esi],13033ACh ; 设置自己的虚表
013019E5 mov eax,dword ptr [eax+4]
013019E8 mov dword ptr [ebp-4],0
013019EF mov dword ptr [eax+esi+4],130341Ch ; 设置虚基类的虚表
013019F7 call dword ptr ds:[130303Ch]
013019FD add esp,4
01301A00 mov eax,esi
01301A02 pop esi
01301A03 mov esp,ebp
01301A05 pop ebp
01301A06 ret 4
__declspec(noinline) virtual void Z1() { printf("Z1 in Z\n"); }
__declspec(noinline) virtual ~Z() { printf("dtor in Z\n"); }
00171A20 mov eax,dword ptr [ecx-4] ; ecx指向的是对象首地址+8处, 所以这里实际上是在取虚基类表指针
00171A23 mov dword ptr [ecx-8],1733ACh ; 设置虚表指针
00171A2A mov eax,dword ptr [eax+4]
00171A2D push 17330Ch
00171A32 mov dword ptr [eax+ecx-4],17341Ch ; 设置虚基类的虚表指针
00171A3A call dword ptr ds:[17303Ch]
00171A40 pop ecx
00171A41 ret
};
class D : public B, public C, public Y, public Z {
int val_;
public:
__declspec(noinline) D() { printf("ctor in D, %d\n", &val_); }
01301A80 push ebp
01301A81 mov ebp,esp
01301A83 sub esp,8
01301A86 push ebx
01301A87 push esi
01301A88 mov esi,ecx
01301A8A push edi
01301A8B lea ecx,[esi+28h]
01301A8E mov dword ptr [ebp-8],0
01301A95 mov dword ptr [esi+4],1303424h
01301A9C mov dword ptr [esi+0Ch],13033F8h
01301AA3 mov dword ptr [esi+14h],13033F8h
01301AAA mov dword ptr [esi+1Ch],1303438h ; 设置4个直接父类的虚基类表指针
01301AB1 call A::A (013016B0h)
01301AB6 lea ecx,[esi+30h]
01301AB9 call X::X (013018A0h)
01301ABE push ecx
01301ABF mov ecx,esi
01301AC1 call B::B (01301740h)
01301AC6 push ecx
01301AC7 lea ecx,[esi+8]
01301ACA call C::C (013017F0h)
01301ACF push ecx
01301AD0 lea ecx,[esi+10h]
01301AD3 call Y::Y (01301920h)
01301AD8 push ecx
01301AD9 lea ecx,[esi+18h]
01301ADC call Z::Z (013019D0h)
01301AE1 mov edx,esi
01301AE3 mov dword ptr [esi+8],1303394h ; 设置C类虚表指针
01301AEA mov eax,dword ptr [edx+4]
01301AED mov dword ptr [edx],13033F0h ; 设置B类和子类共用的虚表指针
01301AF3 mov dword ptr [esi+10h],130338Ch ; 设置Y类虚表指针
01301AFA mov dword ptr [esi+18h],1303414h ; 设置Z类虚表指针
01301B01 mov eax,dword ptr [eax+4]
01301B04 mov dword ptr [eax+edx+4],1303404h ; 设置A类虚表指针
01301B0C mov eax,dword ptr [edx+4]
01301B0F mov eax,dword ptr [eax+8]
01301B12 mov dword ptr [eax+edx+4],13033E4h ; 设置X类虚表指针
01301B1A mov eax,dword ptr [edx+4]
01301B1D mov ecx,dword ptr [eax+4]
01301B20 lea eax,[ecx-24h]
01301B23 mov dword ptr [ecx+edx],eax ; vtordisp
01301B26 mov eax,dword ptr [edx+4]
01301B29 mov ecx,dword ptr [eax+8]
01301B2C lea eax,[ecx-2Ch]
01301B2F mov dword ptr [ecx+edx],eax ; vtordisp
01301B32 lea eax,[edx+20h]
01301B35 push eax
01301B36 push 1303318h
01301B3B call dword ptr ds:[130303Ch]
01301B41 add esp,8
01301B44 mov eax,esi
01301B46 pop edi
01301B47 pop esi
01301B48 pop ebx
01301B49 mov esp,ebp
01301B4B pop ebp
01301B4C ret 4
__declspec(noinline) virtual void A2() { printf("A2 in D\n"); }
__declspec(noinline) virtual void B1() { printf("B1 in D\n"); }
__declspec(noinline) virtual void X1() { printf("X1 in D\n"); }
__declspec(noinline) virtual void Y1() { printf("Y1 in D\n"); }
__declspec(noinline) virtual void Z1() { printf("Z1 in D\n"); }
__declspec(noinline) virtual void D1() { printf("C1 in D\n"); }
__declspec(noinline) virtual ~D() { printf("dtor in D\n"); }
01301BB0 push ebx
01301BB1 push esi
01301BB2 push edi
01301BB3 mov edi,ecx ; ecx是A类子对象首地址, 也就是D类对象首地址+28h处
01301BB5 push 1303370h
01301BBA mov eax,dword ptr [edi-24h] ; 这里实际是取+4h处的虚基类表指针
01301BBD mov dword ptr [edi-28h],13033F0h ; 设置D类和B类共用的虚表指针
01301BC4 mov dword ptr [edi-20h],1303394h ; 设置C类的虚表指针
01301BCB mov dword ptr [edi-18h],130338Ch ; 设置Y类虚表指针
01301BD2 mov dword ptr [edi-10h],1303414h ; 设置Z类虚表指针
01301BD9 mov eax,dword ptr [eax+4]
01301BDC mov dword ptr [eax+edi-24h],1303404h ; 设置A类虚表指针
01301BE4 mov eax,dword ptr [edi-24h]
01301BE7 mov eax,dword ptr [eax+8]
01301BEA mov dword ptr [eax+edi-24h],13033E4h ; 设置X类虚表指针
01301BF2 mov eax,dword ptr [edi-24h]
01301BF5 mov ecx,dword ptr [eax+4]
01301BF8 lea eax,[ecx-24h]
01301BFB mov dword ptr [ecx+edi-28h],eax ; vtordisp
01301BFF mov eax,dword ptr [edi-24h]
01301C02 mov edx,dword ptr [eax+8]
01301C05 lea eax,[edx-2Ch]
01301C08 mov dword ptr [edx+edi-28h],eax ; vtordisp
01301C0C call dword ptr ds:[130303Ch]
01301C12 add esp,4
01301C15 lea ecx,[edi-8] ; 传入这几个析构函数的this实际指向的是this+8的位置
01301C18 call Z::~Z (01301A20h)
01301C1D lea ecx,[edi-10h]
01301C20 call Y::~Y (01301970h)
01301C25 lea ecx,[edi-18h]
01301C28 call C::~C (01301840h)
01301C2D lea ecx,[edi-20h]
01301C30 pop edi
01301C31 pop esi
01301C32 pop ebx
01301C33 jmp B::~B (01301790h) ; 按继承列表相反的顺序析构各直接父类子对象
};
// 这里是delete调用的A类虚表中的析构代理
00171D20 sub ecx,dword ptr [ecx-4] ; 此处ecx是A类子对象的首地址, ecx-4处是vtordisp, 在构造函数中被初始化为0
00171D23 jmp D::`scalar deleting destructor' (0171C40h)
00171C40 push ebp
00171C41 mov ebp,esp
00171C43 push esi
00171C44 push edi
00171C45 lea edi,[ecx-28h]
00171C48 lea ecx,[edi+28h] ; 传递给D类析构函数的this是指向A类子对象的
01301C4B call D::~D (01301BB0h) ; 先执行子类的析构函数
01301C50 lea ecx,[edi+30h]
01301C53 call X::~X (013018D0h) ; 然后才是虚基类析构
01301C58 lea ecx,[edi+28h]
01301C5B call A::~A (013016F0h) ; 虚基类析构按照继承的相反顺序
01301C60 test byte ptr [ebp+8],1
01301C64 je D::`scalar deleting destructor'+30h (01301C70h)
01301C66 push edi
01301C67 call dword ptr ds:[13030B4h] ; 释放堆空间
00171C6D add esp,4
00171C70 mov eax,edi
00171C72 pop edi
00171C73 pop esi
00171C74 pop ebp
00171C75 ret 4
int main()
{
D *pd = new D;
pd->D1();
B *pb = pd;
pb->B1();
C *pc = pd;
pc->C1();
A *pa = pd;
01301CB4 mov eax,dword ptr [edi+4] ; 取虚子类和B类共用的虚基类表的指针
01301CB7 mov ecx,dword ptr [eax+4] ; 取虚基类表中第二项, 也就是A类的偏移
01301CBA add ecx,4 ; 再加上虚基类表指针距对象首地址的偏移, 得到虚基类子对象距子类对象首地址的偏移
01301CBD add ecx,edi ; 再加上子类对象首地址, 最终计算出虚基类子对象首地址
pa->A1();
01301CBF mov eax,dword ptr [ecx]
01301CC1 call dword ptr [eax]
A *paa = pc;
01301CC3 mov eax,dword ptr [edi+0Ch] ; 取C类的虚基类表指针
01301CC6 mov ecx,dword ptr [eax+4] ; 取出表中第二项
01301CC9 add ecx,0Ch ; 加上指针到子类对象首地址的偏移
01301CCC add ecx,edi ; 加上子类对象首地址, 得到虚基类子对象首地址
paa->A2();
01301CCE mov eax,dword ptr [ecx]
01301CD0 call dword ptr [eax+4]
Y *py = pd;
py->Y1();
Z *pz = pd;
pz->Z1();
X *px = pd;
01301CE3 mov eax,dword ptr [edi+4]
01301CE6 mov ecx,dword ptr [eax+8] ; 取出表中的第三项, 也就是第二个虚基类的偏移
01301CE9 add ecx,4
01301CEC add ecx,edi
px->X1();
01301CEE mov eax,dword ptr [ecx]
01301CF0 call dword ptr [eax]
X *pxx = py;
01301CF2 mov eax,dword ptr [edi+14h]
01301CF5 mov ecx,dword ptr [eax+4] ; X是Y类的唯一一个虚基类, 所以使用Y类的虚基类表时, 它依旧是第二项
01301CF8 add ecx,14h
01301CFB add ecx,edi
pxx->X1();
01301CFD mov eax,dword ptr [ecx]
01301CFF call dword ptr [eax]
delete pd;
01301D01 mov eax,dword ptr [edi+4]
01301D04 lea ecx,[edi+4]
01301D07 mov eax,dword ptr [eax+4]
01301D0A add ecx,eax
01301D0C push 1
01301D0E mov eax,dword ptr [ecx]
01301D10 call dword ptr [eax+8] ; 析构时用的析构代理是从第一个虚基类的虚表中查到的
01301D13 pop edi
return 0;
}
|
能力值:
( LV2,RANK:10 )
|
-
-
3 楼
首先, 感谢您的回答, 然还是没有解答我的疑问
针对您所分析的代码
1. 00171A20 mov eax,dword ptr [ecx-4] ; ecx指向的是对象首地址+8处, 所以这里实际上是在取虚基类表指针
==> 我想问的问题就是为什么ecx指向的是对象首地址+8处, 而不是直接的首地址
2. 01301AAA mov dword ptr [esi+1Ch],1303438h ; 设置4个直接父类的虚基类表指针
==> 个人感觉这个注释不对, 应该是设置4个直接父类的虚函数偏移(按照书上说所的, 也就是vt_offset)
|
能力值:
( LV3,RANK:20 )
|
-
-
4 楼
[QUOTE=hzzlyz;1236549]首先, 感谢您的回答, 然还是没有解答我的疑问
针对您所分析的代码
1. 00171A20 mov eax,dword ptr [ecx-4] ; ecx指向的是对象首地址+8处, 所以这里实际上是在取虚基类表指针
==> 我想问的问题就是为什么ecx指...[/QUOTE]
1. 我也没弄明白为什么传进来的是this+8, 但是分析D类的析构, 调用的时候ecx的确是this+8的
2. 可能是我用的术语不太规范...自己造的, 那个指针正确的叫法应该是vbptr, 表里是虚基类的偏移, 具体的看一下D的内存布局吧
class D size(52):
+---
| +--- (base class B)
0 | | {vfptr}
4 | | {vbptr}
| +---
| +--- (base class C)
8 | | {vfptr}
12 | | {vbptr}
| +---
| +--- (base class Y)
16 | | {vfptr}
20 | | {vbptr}
| +---
| +--- (base class Z)
24 | | {vfptr}
28 | | {vbptr}
| +---
32 | val_
+---
36 | (vtordisp for vbase A)
+--- (virtual base A)
40 | {vfptr}
+---
44 | (vtordisp for vbase X)
+--- (virtual base X)
48 | {vfptr}
+---
D::$vftable@B@:
| &D_meta
| 0
0 | &D::B1
1 | &D::D1
D::$vftable@C@:
| -8
0 | &C::C1
D::$vftable@Y@:
| -16
0 | &D::Y1
D::$vftable@Z@:
| -24
0 | &D::Z1
D::$vbtable@B@:
0 | -4
1 | 36 (Dd(B+4)A)
2 | 44 (Dd(D+4)X)
D::$vbtable@C@:
0 | -4
1 | 28 (Dd(C+4)A)
D::$vbtable@Y@:
0 | -4
1 | 28 (Dd(Y+4)X)
D::$vbtable@Z@:
0 | -4
1 | 20 (Dd(Z+4)X)
D::$vftable@A@:
| -40
0 | &A::A1
1 | &(vtordisp) D::A2
2 | &(vtordisp) D::{dtor}
D::$vftable@X@:
| -48
0 | &(vtordisp) D::X1
1 | &(vtordisp) thunk: this-=8; goto D::{dtor}
|