逆向笔记(八)

要努力了,在不努力就废了。

这是最后一篇了,大体就是这样,有遗漏的部分后续会继续补充,接下来在整理一些常用算法识别的相关内容。
如果碰巧有各位大大看到了欢迎指点一二,谢谢。

逆向笔记之虚函数,继承与多重继承。

虚函数

对象的多态性需要通过虚表和虚表指针来完成,虚表指针被定义在对象首地址的前4字节处,因此虚函数必须作为函数成员函数使用。因为非成员函数没有this指针,无法获取虚表指针,也就无法获取虚表,也就无法访问虚函数。

虚函数的机制

在C++中,使用关键字virtual声明函数为虚函数。当类中定义有虚函数时,编译器会将该类中所有虚函数的首地址保存在一张地址表中,这张表被称为虚函数地址表,简称虚表。同时编译器还会在类中添加一个隐藏数据成员,称为虚表指针。该指针保存着虚表首地址,用于记录和查找虚函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
C代码:
class CVirtual
{
public:
virtual int GetNum()
{
return m_nNum;
}
virtual void SetNum(int nNum)
{
m_nNum = nNum;
}
private:
int m_nNum;
};
int _tmain(int argc, _TCHAR* argv[])
{
int nSize = sizeof(CVirtual);
CVirtual ogj;
return 0;
}

这里定义了两个虚函数和一个数据成员。这里类的的大小为8,因为定义了虚函数后,含有虚表指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
对应反汇编Debug:
Main函数
01111B20 > 55 push ebp
01111B21 8BEC mov ebp,esp
01111B23 81EC DC000000 sub esp,0xDC
01111B29 53 push ebx
01111B2A 56 push esi
01111B2B 57 push edi
01111B2C 8DBD 24FFFFFF lea edi,dword ptr ss:[ebp-0xDC]
01111B32 B9 37000000 mov ecx,0x37
01111B37 B8 CCCCCCCC mov eax,0xCCCCCCCC
01111B3C F3:AB rep stos dword ptr es:[edi]
01111B3E C745 F8 0800000>mov dword ptr ss:[ebp-0x8],0x8
01111B45 8D4D E8 lea ecx,dword ptr ss:[ebp-0x18] //获取对象首地址
01111B48 E8 CFF6FFFF call TestCode.0111121C //调用构造函数,这里调用的是编译器提供的默认构造函数
01111B4D 33C0 xor eax,eax
01111B4F 52 push edx
01111B50 8BCD mov ecx,ebp
01111B52 50 push eax
01111B53 8D15 741B1101 lea edx,dword ptr ds:[0x1111B74]
01111B59 E8 42F5FFFF call TestCode.011110A0
01111B5E 58 pop eax
01111B5F 5A pop edx
01111B60 5F pop edi
01111B61 5E pop esi
01111B62 5B pop ebx
01111B63 81C4 DC000000 add esp,0xDC
01111B69 3BEC cmp ebp,esp
01111B6B E8 11F6FFFF call TestCode.01111181
01111B70 8BE5 mov esp,ebp
01111B72 5D pop ebp
01111B73 C3 retn
默认构造函数
011115A0 > 55 push ebp
011115A1 8BEC mov ebp,esp
011115A3 81EC CC000000 sub esp,0xCC
011115A9 53 push ebx
011115AA 56 push esi
011115AB 57 push edi
011115AC 51 push ecx
011115AD 8DBD 34FFFFFF lea edi,dword ptr ss:[ebp-0xCC]
011115B3 B9 33000000 mov ecx,0x33
011115B8 B8 CCCCCCCC mov eax,0xCCCCCCCC
011115BD F3:AB rep stos dword ptr es:[edi]
011115BF 59 pop ecx //还原this指针
011115C0 894D F8 mov dword ptr ss:[ebp-0x8],ecx
011115C3 8B45 F8 mov eax,dword ptr ss:[ebp-0x8] //eax为this指针
011115C6 C700 88781101 mov dword ptr ds:[eax],offset TestCode.CVirtual::`vftable' //保存虚表指针
011115CC 8B45 F8 mov eax,dword ptr ss:[ebp-0x8] //返回this指针
011115CF 5F pop edi
011115D0 5E pop esi
011115D1 5B pop ebx
011115D2 8BE5 mov esp,ebp
011115D4 5D pop ebp
011115D5 C3 retn

虚函数的识别
在判断是否为虚函数时,我们需要鉴别类中是否出现这样的特征:
1)类中隐式定义了一个数据成员
2)该数据成员在首地址处,占4个字节
3)构造函数会将此数据成员初始化为某个数组的首地址
4)这个地址属于数据区,是相对固定的地址
5)在这个数组内,每个元素都是函数指针
6)这些函数在调用时第一个参数必然为this指针
7)在这些函数内部,很有可能会对this指针使用相对间接寻址的访问方式

从内存角度看继承和多重继承

识别类和类之间的关系

在C++的继承关系中,子类具备父类所有的成员数据和成员函数。子类对象可以直接使用父类中声明为公有和保护的数据成员和成员函数。在父类中声明为私有(private)的成员,虽然子类对象无法直接访问,但是在子类对象的内存结构中,父类私有的成员数据依然存在。C++语法规定的访问控制仅限于编译层面,在编译的过程中由编译器进行语法检查,因此访问控制不会影响对象的内存结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
C代码:
class CBase
{
public:
CBase()
{
printf("CBase\n");
}
~CBase()
{
printf("~CBase\n");
}
void SetNum(int nNum)
{
m_nBase = nNum;
}
int GetNum()
{
return m_nBase;
}
int m_nBase;
};
class CDervie : public CBase
{
public:
void ShowNum(int nNum)
{
SetNum(nNum);
m_nDervie = nNum + 1;
printf("%d\n", GetNum());
printf("%d\n", m_nDervie);
}
int m_nDervie;
};
int _tmain(int argc, _TCHAR* argv[])
{
CDervie obj;
obj.ShowNum(argc);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
对应反汇编Debug:
Main函数
00AF1D10 > 55 push ebp
00AF1D11 8BEC mov ebp,esp
00AF1D13 6A FF push -0x1
00AF1D15 68 A855AF00 push TestCode.00AF55A8
00AF1D1A 64:A1 00000000 mov eax,dword ptr fs:[0]
00AF1D20 50 push eax
00AF1D21 81EC DC000000 sub esp,0xDC
00AF1D27 53 push ebx
00AF1D28 56 push esi
00AF1D29 57 push edi
00AF1D2A 8DBD 18FFFFFF lea edi,dword ptr ss:[ebp-0xE8]
00AF1D30 B9 37000000 mov ecx,0x37
00AF1D35 B8 CCCCCCCC mov eax,0xCCCCCCCC
00AF1D3A F3:AB rep stos dword ptr es:[edi]
00AF1D3C A1 04A0AF00 mov eax,dword ptr ds:[__security_cookie]
00AF1D41 33C5 xor eax,ebp
00AF1D43 50 push eax
00AF1D44 8D45 F4 lea eax,dword ptr ss:[ebp-0xC]
00AF1D47 64:A3 00000000 mov dword ptr fs:[0],eax
00AF1D4D 8D4D E8 lea ecx,dword ptr ss:[ebp-0x18]//取对象首地址
00AF1D50 E8 E7F2FFFF call TestCode.00AF103C//调用子类构造函数
00AF1D55 C745 FC 0000000>mov dword ptr ss:[ebp-0x4],0x0
00AF1D5C 8B45 08 mov eax,dword ptr ss:[ebp+0x8]
00AF1D5F 50 push eax
00AF1D60 8D4D E8 lea ecx,dword ptr ss:[ebp-0x18] //取对象首地址
00AF1D63 E8 B1F2FFFF call TestCode.00AF1019 //调用ShowNum函数
00AF1D68 C785 1CFFFFFF 0>mov dword ptr ss:[ebp-0xE4],0x0
00AF1D72 C745 FC FFFFFFF>mov dword ptr ss:[ebp-0x4],-0x1
00AF1D79 8D4D E8 lea ecx,dword ptr ss:[ebp-0x18]
00AF1D7C E8 B6F2FFFF call TestCode.00AF1037 //调用子类析构函数
00AF1D81 8B85 1CFFFFFF mov eax,dword ptr ss:[ebp-0xE4]
00AF1D87 52 push edx
00AF1D88 8BCD mov ecx,ebp
00AF1D8A 50 push eax
00AF1D8B 8D15 B81DAF00 lea edx,dword ptr ds:[0xAF1DB8]
00AF1D91 E8 19F3FFFF call TestCode.00AF10AF
00AF1D96 58 pop eax
00AF1D97 5A pop edx
00AF1D98 8B4D F4 mov ecx,dword ptr ss:[ebp-0xC]
00AF1D9B 64:890D 0000000>mov dword ptr fs:[0],ecx
00AF1DA2 59 pop ecx
00AF1DA3 5F pop edi
00AF1DA4 5E pop esi
00AF1DA5 5B pop ebx
00AF1DA6 81C4 E8000000 add esp,0xE8
00AF1DAC 3BEC cmp ebp,esp
00AF1DAE E8 D3F3FFFF call TestCode.00AF1186
00AF1DB3 8BE5 mov esp,ebp
00AF1DB5 5D pop ebp
00AF1DB6 C3 retn
构造函数
00AF1500 > 55 push ebp
00AF1501 8BEC mov ebp,esp
00AF1503 81EC CC000000 sub esp,0xCC
00AF1509 53 push ebx
00AF150A 56 push esi
00AF150B 57 push edi
00AF150C 51 push ecx
00AF150D 8DBD 34FFFFFF lea edi,dword ptr ss:[ebp-0xCC]
00AF1513 B9 33000000 mov ecx,0x33
00AF1518 B8 CCCCCCCC mov eax,0xCCCCCCCC
00AF151D F3:AB rep stos dword ptr es:[edi]
00AF151F 59 pop ecx //还原this指针
00AF1520 894D F8 mov dword ptr ss:[ebp-0x8],ecx
00AF1523 8B4D F8 mov ecx,dword ptr ss:[ebp-0x8] //this指针给ecx
00AF1526 E8 6FFCFFFF call TestCode.00AF119A //调用父类构造函数
00AF152B 8B45 F8 mov eax,dword ptr ss:[ebp-0x8]
00AF152E 5F pop edi
00AF152F 5E pop esi
00AF1530 5B pop ebx
00AF1531 81C4 CC000000 add esp,0xCC
00AF1537 3BEC cmp ebp,esp
00AF1539 E8 48FCFFFF call TestCode.00AF1186
00AF153E 8BE5 mov esp,ebp
00AF1540 5D pop ebp
00AF1541 C3 retn
析构函数
00AF16D0 > 55 push ebp
00AF16D1 8BEC mov ebp,esp
00AF16D3 81EC CC000000 sub esp,0xCC
00AF16D9 53 push ebx
00AF16DA 56 push esi
00AF16DB 57 push edi
00AF16DC 51 push ecx
00AF16DD 8DBD 34FFFFFF lea edi,dword ptr ss:[ebp-0xCC]
00AF16E3 B9 33000000 mov ecx,0x33
00AF16E8 B8 CCCCCCCC mov eax,0xCCCCCCCC
00AF16ED F3:AB rep stos dword ptr es:[edi]
00AF16EF 59 pop ecx
00AF16F0 894D F8 mov dword ptr ss:[ebp-0x8],ecx
00AF16F3 8B4D F8 mov ecx,dword ptr ss:[ebp-0x8]//this指针给ecx
00AF16F6 E8 32F9FFFF call TestCode.00AF102D //调用父类析构函数
00AF16FB 5F pop edi
00AF16FC 5E pop esi
00AF16FD 5B pop ebx
00AF16FE 81C4 CC000000 add esp,0xCC
00AF1704 3BEC cmp ebp,esp
00AF1706 E8 7BFAFFFF call TestCode.00AF1186
00AF170B 8BE5 mov esp,ebp
00AF170D 5D pop ebp
00AF170E C3 retn

由此可以看出当子类中没有构造函数或析构函数,而其父类却需要构造函数与析构函数时,编译器会为该父类的子类提供默认的构造函数与析构函数。还可以看出子类对象在内存中的数据排列方式,先安排父类的数据,然后安排子类新定义的数据。

接着来看一下当类中定义了其他对象作为成员,并在初始化列表中指定了某个成员的初始化值时构造的顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
C代码:
class CBase
{
public:
CBase()
{
printf("CBase\n");
}
~CBase()
{
printf("~CBase\n");
}
void SetNum(int nNum)
{
m_nBase = nNum;
}
int GetNum()
{
return m_nBase;
}
int m_nBase;
};
class CInit
{
public:
CInit()
{
m_nNum = 0;
}
int m_nNum;
};
class CDervie : public CBase
{
public:
CDervie() : m_nDervie(1)//初始化列表
{
printf("CDervie\n");
}
CInit m_Init;
int m_nDervie;
};
int _tmain(int argc, _TCHAR* argv[])
{
CDervie obj;
obj.ShowNum(argc);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
对应反汇编Debug:
子类构造函数
00051C90 > 55 push ebp
00051C91 8BEC mov ebp,esp
00051C93 6A FF push -0x1
00051C95 68 78550500 push TestCode.00055578
00051C9A 64:A1 00000000 mov eax,dword ptr fs:[0]
00051CA0 50 push eax
00051CA1 81EC CC000000 sub esp,0xCC
00051CA7 53 push ebx
00051CA8 56 push esi
00051CA9 57 push edi
00051CAA 51 push ecx
00051CAB 8DBD 28FFFFFF lea edi,dword ptr ss:[ebp-0xD8]
00051CB1 B9 33000000 mov ecx,0x33
00051CB6 B8 CCCCCCCC mov eax,0xCCCCCCCC
00051CBB F3:AB rep stos dword ptr es:[edi]
00051CBD 59 pop ecx //还原this指针
00051CBE A1 04A00500 mov eax,dword ptr ds:[__security_cookie]
00051CC3 33C5 xor eax,ebp
00051CC5 50 push eax
00051CC6 8D45 F4 lea eax,dword ptr ss:[ebp-0xC]
00051CC9 64:A3 00000000 mov dword ptr fs:[0],eax
00051CCF 894D EC mov dword ptr ss:[ebp-0x14],ecx
00051CD2 8B4D EC mov ecx,dword ptr ss:[ebp-0x14] //this指针给ecx
00051CD5 E8 74F5FFFF call TestCode.0005124E //调用父类构造函数
00051CDA C745 FC 0000000>mov dword ptr ss:[ebp-0x4],0x0
00051CE1 8B4D EC mov ecx,dword ptr ss:[ebp-0x14] //this指针给ecx
00051CE4 83C1 04 add ecx,0x4 //ecx + 4
00051CE7 E8 58F5FFFF call TestCode.00051244 //调用CInit构造函数
00051CEC 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00051CEF C740 08 0100000>mov dword ptr ds:[eax+0x8],0x1 //初始化数据成员
00051CF6 8BF4 mov esi,esp
00051CF8 68 98780500 push TestCode.00057898 ; ASCII "CDervie\n"
00051CFD FF15 1CB10500 call dword ptr ds:[<&MSVCR120D.printf>] ; MSVCR120.printf
00051D03 83C4 04 add esp,0x4
00051D06 3BF4 cmp esi,esp
00051D08 E8 79F4FFFF call TestCode.00051186
00051D0D C745 FC FFFFFFF>mov dword ptr ss:[ebp-0x4],-0x1
00051D14 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00051D17 8B4D F4 mov ecx,dword ptr ss:[ebp-0xC]
00051D1A 64:890D 0000000>mov dword ptr fs:[0],ecx
00051D21 59 pop ecx
00051D22 5F pop edi
00051D23 5E pop esi
00051D24 5B pop ebx
00051D25 81C4 D8000000 add esp,0xD8
00051D2B 3BEC cmp ebp,esp
00051D2D E8 54F4FFFF call TestCode.00051186
00051D32 8BE5 mov esp,ebp
00051D34 5D pop ebp
00051D35 C3 retn

由此可以看出在有初始化列表的情况下,将会优先执行初始化列表中的操作,其次才是自身的构造函数。构造的顺序为:先构造父类,然后按声明顺序构造成员对象和初始化列表中的指定成员,最后才是自身的构造函数。

因为有虚表指针,调用虚函数的方式改为查表并间接调用,在虚表中得到函数首地址并跳转到此地址处执行代码。利用此特性即可通过父类指针访问不同的派生类。在调用父类中定义的虚函数时,根据指针所指向的对象中的虚表指针,可得到虚表信息,间接调用虚函数,即构成多态。具体看个例子吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
C代码:
class CPerson
{
public:
CPerson(){}
virtual ~CPerson(){}
virtual void ShowSpeak(){}
};
class CChinese : public CPerson
{
public:
CChinese(){}
virtual ~CChinese(){}
virtual void ShowSpeak()
{
printf("chinese\n");
}
};
class CAmerican : public CPerson
{
public:
CAmerican(){}
virtual ~CAmerican(){}
virtual void ShowSpeak()
{
printf("CAmerican\n");
}
};
void Speak(CPerson *obj)
{
obj->ShowSpeak();
}
int _tmain(int argc, _TCHAR* argv[])
{
CChinese chinese;
CAmerican american;
Speak(&chinese);
Speak(&american);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
对应反汇编Debug:
Speak入口
00A61FF8 8D45 EC lea eax,dword ptr ss:[ebp-0x14] //取对象首地址
00A61FFB 50 push eax //压栈
00A61FFC E8 26F1FFFF call TestCode.00A61127 //调用
00A62001 83C4 04 add esp,0x4 //堆栈平衡
Speak函数实现
00A61F40 > 55 push ebp
00A61F41 8BEC mov ebp,esp
00A61F43 81EC C0000000 sub esp,0xC0
00A61F49 53 push ebx
00A61F4A 56 push esi
00A61F4B 57 push edi
00A61F4C 8DBD 40FFFFFF lea edi,dword ptr ss:[ebp-0xC0]
00A61F52 B9 30000000 mov ecx,0x30
00A61F57 B8 CCCCCCCC mov eax,0xCCCCCCCC
00A61F5C F3:AB rep stos dword ptr es:[edi]
00A61F5E 8B45 08 mov eax,dword ptr ss:[ebp+0x8] //参数给eax
00A61F61 8B10 mov edx,dword ptr ds:[eax] //取eax内容给edx,也就是虚表首地址
00A61F63 8BF4 mov esi,esp
00A61F65 8B4D 08 mov ecx,dword ptr ss:[ebp+0x8]
00A61F68 8B42 04 mov eax,dword ptr ds:[edx+0x4] //寻址虚表第二项
00A61F6B FFD0 call eax //调用speak
00A61F6D 3BF4 cmp esi,esp
00A61F6F E8 49F2FFFF call TestCode.00A611BD
00A61F74 5F pop edi
00A61F75 5E pop esi
00A61F76 5B pop ebx
00A61F77 81C4 C0000000 add esp,0xC0
00A61F7D 3BEC cmp ebp,esp
00A61F7F E8 39F2FFFF call TestCode.00A611BD
00A61F84 8BE5 mov esp,ebp
00A61F86 5D pop ebp
00A61F87 C3 retn

由此可以看出虚函数的调用过程使用了间接寻找方式 。还有要将析构函数定义为虚函数,因为父类指针保存子类对象的首地址,因此当使用父类指针指向子类堆对象时会出问题。当使用delete释放对象空间时,如果析构函数没有被定义为虚函数,编译器将会按指针的类型调用父类析构函数也会出问题。而使用虚析构函数后会访问虚表并调用对象的析构函数。

多重继承

直接看例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
C代码:
class CSofa
{
public:
CSofa()
{
m_nColor = 2;
}
virtual ~CSofa()
{
printf("virtual ~CSofa\n");
}
virtual int GetColor()
{
return m_nColor;
}
virtual int SitDowm()
{
return printf("SitDowm\n");
}
int m_nColor;
};
class CBed
{
public:
CBed()
{
m_nLength = 4;
m_nWidth = 5;
}
virtual ~CBed()
{
printf("virtual ~CBed\n");
}
virtual int GetArea()
{
return m_nLength * m_nWidth;
}
virtual int Sleep()
{
return printf("goto sleep\n");
}
int m_nLength;
int m_nWidth;
};
class CSofaBed : public CSofa,public CBed
{
public:
CSofaBed()
{
m_nHeight = 6;
}
virtual ~CSofaBed()
{
printf("virtual ~CSofaBed\n");
}
virtual int SitDowm()
{
return printf("SitDowm\n");
}
virtual int Sleep()
{
return printf("goto sleep\n");
}
virtual int GetHeight()
{
return m_nHeight;
}
int m_nHeight;
};
int _tmain(int argc, _TCHAR* argv[])
{
CSofaBed obj;
int n = sizeof(obj);
return 0;
}

这里对象大小为24,内存布局如下

由此可以看出数据成员的排列顺序由继承父类的先后顺序决定,从左向右依次排列。此外内存中的两个地址是虚表指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
对应反汇编Debug:
CSofaBed构造函数
00C149E0 > 55 push ebp
00C149E1 8BEC mov ebp,esp
00C149E3 6A FF push -0x1
00C149E5 68 A85AC100 push TestCode.00C15AA8
00C149EA 64:A1 00000000 mov eax,dword ptr fs:[0]
00C149F0 50 push eax
00C149F1 81EC CC000000 sub esp,0xCC
00C149F7 53 push ebx
00C149F8 56 push esi
00C149F9 57 push edi
00C149FA 51 push ecx
00C149FB 8DBD 28FFFFFF lea edi,dword ptr ss:[ebp-0xD8]
00C14A01 B9 33000000 mov ecx,0x33
00C14A06 B8 CCCCCCCC mov eax,0xCCCCCCCC
00C14A0B F3:AB rep stos dword ptr es:[edi]
00C14A0D 59 pop ecx //还原this指针
00C14A0E A1 04A0C100 mov eax,dword ptr ds:[__security_cookie]
00C14A13 33C5 xor eax,ebp
00C14A15 50 push eax
00C14A16 8D45 F4 lea eax,dword ptr ss:[ebp-0xC]
00C14A19 64:A3 00000000 mov dword ptr fs:[0],eax
00C14A1F 894D EC mov dword ptr ss:[ebp-0x14],ecx
00C14A22 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
00C14A25 E8 ABC8FFFF call TestCode.00C112D5 //调用父类CSofa构造函数
00C14A2A C745 FC 0000000>mov dword ptr ss:[ebp-0x4],0x0
00C14A31 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
00C14A34 83C1 08 add ecx,0x8 //调整this指针到第二个虚表指针地址处
00C14A37 E8 B2C8FFFF call TestCode.00C112EE //调用父类CBed构造函数
00C14A3C 8B45 EC mov eax,dword ptr ss:[ebp-0x14] //this指针给eax
00C14A3F C700 507AC100 mov dword ptr ds:[eax],offset TestCode.CSofaBed::`vftable' //写入虚表指针
00C14A45 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00C14A48 C740 08 A479C10>mov dword ptr ds:[eax+0x8],offset TestCode.CSofaBed::`vftable' //写入虚表指针
00C14A4F 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00C14A52 C740 14 0600000>mov dword ptr ds:[eax+0x14],0x6
00C14A59 C745 FC FFFFFFF>mov dword ptr ss:[ebp-0x4],-0x1
00C14A60 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00C14A63 8B4D F4 mov ecx,dword ptr ss:[ebp-0xC]
00C14A66 64:890D 0000000>mov dword ptr fs:[0],ecx
00C14A6D 59 pop ecx
00C14A6E 5F pop edi
00C14A6F 5E pop esi
00C14A70 5B pop ebx
00C14A71 81C4 D8000000 add esp,0xD8
00C14A77 3BEC cmp ebp,esp
00C14A79 E8 3FC7FFFF call TestCode.00C111BD
00C14A7E 8BE5 mov esp,ebp
00C14A80 5D pop ebp
00C14A81 C3 retn

由此可以看出根据继承关系的顺序首先调用父类CSofa构造函数,在调用另一个父类CBed时,并不是直接将对象首地址做this指针传递,而是向后调整父类CSofa大小,用调整后的值做this指针调用构造。由于有两个父类,因此子类在继承时也将他们的虚表指针一起继承过来,也就有了两个虚表指针。可见,在多重继承中,子类虚表指针的个数取决于所继承父类的个数。

再来看看析构过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
对应反汇编Debug:
00C11E70 > 55 push ebp
00C11E71 8BEC mov ebp,esp
00C11E73 6A FF push -0x1
00C11E75 68 085BC100 push TestCode.00C15B08
00C11E7A 64:A1 00000000 mov eax,dword ptr fs:[0]
00C11E80 50 push eax
00C11E81 81EC CC000000 sub esp,0xCC
00C11E87 53 push ebx
00C11E88 56 push esi
00C11E89 57 push edi
00C11E8A 51 push ecx
00C11E8B 8DBD 28FFFFFF lea edi,dword ptr ss:[ebp-0xD8]
00C11E91 B9 33000000 mov ecx,0x33
00C11E96 B8 CCCCCCCC mov eax,0xCCCCCCCC
00C11E9B F3:AB rep stos dword ptr es:[edi]
00C11E9D 59 pop ecx //还原this指针
00C11E9E A1 04A0C100 mov eax,dword ptr ds:[__security_cookie]
00C11EA3 33C5 xor eax,ebp
00C11EA5 50 push eax
00C11EA6 8D45 F4 lea eax,dword ptr ss:[ebp-0xC]
00C11EA9 64:A3 00000000 mov dword ptr fs:[0],eax
00C11EAF 894D EC mov dword ptr ss:[ebp-0x14],ecx
00C11EB2 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00C11EB5 C700 507AC100 mov dword ptr ds:[eax],offset TestCode.CSofaBed::`vftable'
00C11EBB 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00C11EBE C740 08 A479C10>mov dword ptr ds:[eax+0x8],offset TestCode.CSofaBed::`vftable' //将两个虚表指针设置为各个父类的虚表首地址
00C11EC5 C745 FC 0000000>mov dword ptr ss:[ebp-0x4],0x0
00C11ECC 8BF4 mov esi,esp
00C11ECE 68 507BC100 push TestCode.00C17B50 ; ASCII "virtual ~CSofaBed\n"
00C11ED3 FF15 20B1C100 call dword ptr ds:[<&MSVCR120D.printf>] ; MSVCR120.printf
00C11ED9 83C4 04 add esp,0x4
00C11EDC 3BF4 cmp esi,esp
00C11EDE E8 DAF2FFFF call TestCode.00C111BD
00C11EE3 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
00C11EE6 83C1 08 add ecx,0x8 //调整this指针
00C11EE9 E8 D3F3FFFF call TestCode.00C112C1 //调用CBed析构
00C11EEE C745 FC FFFFFFF>mov dword ptr ss:[ebp-0x4],-0x1
00C11EF5 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
00C11EF8 E8 ECF3FFFF call TestCode.00C112E9 //调用CSofa析构
00C11EFD 8B4D F4 mov ecx,dword ptr ss:[ebp-0xC]
00C11F00 64:890D 0000000>mov dword ptr fs:[0],ecx
00C11F07 59 pop ecx
00C11F08 5F pop edi
00C11F09 5E pop esi
00C11F0A 5B pop ebx
00C11F0B 81C4 D8000000 add esp,0xD8
00C11F11 3BEC cmp ebp,esp
00C11F13 E8 A5F2FFFF call TestCode.00C111BD
00C11F18 8BE5 mov esp,ebp
00C11F1A 5D pop ebp
00C11F1B C3 retn

由此可以看出具有多个同级父类,因此在子类中产生多个虚表指针。在对父类进行析构时,需要设置this指针用于调用父类析构函数。由于具有多个父类,当在析构的过程中调用哥哥父类的析构函数时,传递的首地址将有所不同,编译器会根据每个父类在对象中占用的空间位置,对应传入各个父类部分的首地址作为this指针。

单继承类与多继承类特征总结:

单继承类
在类对象占用的内存空间中,只保存一份虚表指针
由于只有一个虚表指针,对应的只有一个虚表
虚表中各项保存了类中虚函数的首地址
构造时先构造父类,在构造自身,并且只调用一次父类构造函数
析构时先析构自身,在析构父类,并且只调用一次父类析构函数

多重继承类
在类对象所占用的内存空间中,根据继承父类的个数保存对应的虚表指针
根据所保存的虚表指针个数,对应产生相应个数的虚表
转换父类指针时,需要跳转到对象的首地址
构造时需要调用多个父类构造函数
构造时先构造继承列表中第一个父类,然后依次调用到最后一个继承的父类构造函数
析构时先析构自身,然后以与构造函数相反的顺序调用所有父类的析构函数
当对象作为成员时,整个类对象的内存结构和多重继承很相似。当类中无虚函数时,整个类 对象内存结构和多重继承完全一样,当父类或成员对象存在虚函数时,通过观察虚表指针的 位置和构造函数,析构函数中填写虚表指针的数目及目标地址,来还原继承或成员关系。

虚基类

虚基类也被称为抽象类。虚基类的定义需要配合虚函数使用,在虚函数的声明结尾处添加“=0”,这种虚函数被称为纯虚函数,纯虚函数是一个没有实现只有声明的函数,他的存在是为了让类具有虚基类的功能,让继承虚基类的子类都具有虚表以及虚表指针。

直接看例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
C代码:
class CVirtualBase
{
public:
virtual void Show() = 0;
};
class CVirtualChild : public CVirtualBase
{
public:
virtual void Show()
{
printf("CVirtualBase");
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CVirtualChild obj;
obj.Show();
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
对应反汇编Debug:
CVirtualBase构造
00CF15E0 > 55 push ebp
00CF15E1 8BEC mov ebp,esp
00CF15E3 81EC CC000000 sub esp,0xCC
00CF15E9 53 push ebx
00CF15EA 56 push esi
00CF15EB 57 push edi
00CF15EC 51 push ecx
00CF15ED 8DBD 34FFFFFF lea edi,dword ptr ss:[ebp-0xCC]
00CF15F3 B9 33000000 mov ecx,0x33
00CF15F8 B8 CCCCCCCC mov eax,0xCCCCCCCC
00CF15FD F3:AB rep stos dword ptr es:[edi]
00CF15FF 59 pop ecx
00CF1600 894D F8 mov dword ptr ss:[ebp-0x8],ecx
00CF1603 8B45 F8 mov eax,dword ptr ss:[ebp-0x8]
00CF1606 C700 8878CF00 mov dword ptr ds:[eax],offset TestCode.CVirtualBase::`vftable' //设置虚表指针
00CF160C 8B45 F8 mov eax,dword ptr ss:[ebp-0x8]
00CF160F 5F pop edi
00CF1610 5E pop esi
00CF1611 5B pop ebx
00CF1612 8BE5 mov esp,ebp
00CF1614 5D pop ebp
00CF1615 C3 retn

虚表中第一项所指向的函数地址

由此可以看出在虚基类CVirtualBase的虚表信息中,由于虚函数没有实现代码,因此没有首地址,编译器为了防止误调用纯虚函数,将虚表中保存的纯虚函数首地址替换成函数_purecall,用于结束程序,并发出错误信息0x19。根据这个特性但凡在虚表中发现_purecall函数地址时,就可以高度怀疑此虚表对应的是一个虚基类。

菱形继承

菱形继承是最复杂的对象结构,菱形结构会将单一继承与多重继承进行组合,如下图所示


直接看例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
C代码:
class CFurniture
{
public:
CFurniture()
{
m_nPrice = 0;
}
virtual ~CFurniture()
{
printf("virtual ~CFurniture\n");
}
virtual int GetPrice()
{
return m_nPrice;
}
int m_nPrice;
};
class CSofa : virtual public CFurniture
{
public:
CSofa()
{
m_nPrice = 1;
m_nColor = 2;
}
virtual ~CSofa()
{
printf("virtual ~CSofa\n");
}
virtual int GetColor()
{
return m_nColor;
}
virtual int SitDowm()
{
return printf("SitDowm\n");
}
int m_nColor;
};
class CBed : virtual public CFurniture
{
public:
CBed()
{
m_nPrice = 3;
m_nLength = 4;
m_nWidth = 5;
}
virtual ~CBed()
{
printf("virtual ~CBed\n");
}
virtual int GetArea()
{
return m_nLength * m_nWidth;
}
virtual int Sleep()
{
return printf("goto sleep\n");
}
int m_nLength;
int m_nWidth;
};
class CSofaBed : public CSofa,public CBed
{
public:
CSofaBed()
{
m_nHeight = 6;
}
virtual ~CSofaBed()
{
printf("virtual ~CSofaBed\n");
}
virtual int SitDowm()
{
return printf("SitDowm\n");
}
virtual int Sleep()
{
return printf("goto sleep\n");
}
virtual int GetHeight()
{
return m_nHeight;
}
int m_nHeight;
};
int _tmain(int argc, _TCHAR* argv[])
{
CSofaBed obj;
return 0;
}
//加入父类指针转换后
int _tmain(int argc, _TCHAR* argv[])
{
CSofaBed sofabed;
CFurniture *pFurniture = &sofabed;
CSofa *pSofa = &sofabed;
CBed *pBed = &sofabed;
return 0;
}

首先看一下内存结构。

依次为基类未定义的虚函数,基类定义虚函数偏移,数据成员,基类未定义的虚函数,基类定义虚函数偏移,数据成员,数据成员,数据成员,CFurniture定义,数据成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
对应反汇编Debug:
Main函数
01214D20 > 55 push ebp
01214D21 8BEC mov ebp,esp
01214D23 81EC 24010000 sub esp,0x124
01214D29 53 push ebx
01214D2A 56 push esi
01214D2B 57 push edi
01214D2C 8DBD DCFEFFFF lea edi,dword ptr ss:[ebp-0x124]
01214D32 B9 49000000 mov ecx,0x49
01214D37 B8 CCCCCCCC mov eax,0xCCCCCCCC
01214D3C F3:AB rep stos dword ptr es:[edi]
01214D3E 6A 01 push 0x1 //是否构造祖父类的标志,true表示构造,false表示不构造
01214D40 8D4D D4 lea ecx,dword ptr ss:[ebp-0x2C] //取对象首地址
01214D43 E8 C9C5FFFF call TestCode.01211311 //调用CSofaBed构造
01214D48 8D45 D4 lea eax,dword ptr ss:[ebp-0x2C] //取对象首地址
01214D4B 85C0 test eax,eax //检查
01214D4D 75 0C jnz short TestCode.01214D5B
01214D4F C785 DCFEFFFF 0>mov dword ptr ss:[ebp-0x124],0x0
01214D59 EB 10 jmp short TestCode.01214D6B
01214D5B 8B4D D8 mov ecx,dword ptr ss:[ebp-0x28] //取对象第二项数据vt_offset给ecx
01214D5E 8B51 04 mov edx,dword ptr ds:[ecx+0x4] //取出偏移值给edx
01214D61 8D4415 D8 lea eax,dword ptr ss:[ebp+edx-0x28] //得到祖父类数据所在地址
01214D65 8985 DCFEFFFF mov dword ptr ss:[ebp-0x124],eax //保存
01214D6B 8B8D DCFEFFFF mov ecx,dword ptr ss:[ebp-0x124] //赋值pFurniture
01214D71 894D C8 mov dword ptr ss:[ebp-0x38],ecx
01214D74 8D45 D4 lea eax,dword ptr ss:[ebp-0x2C] //直接转换SofaBed对象首地址为父类CSofa的指针
01214D77 8945 BC mov dword ptr ss:[ebp-0x44],eax
01214D7A 8D45 D4 lea eax,dword ptr ss:[ebp-0x2C] //取对象首地址
01214D7D 85C0 test eax,eax
01214D7F 74 0E je short TestCode.01214D8F
01214D81 8D4D D4 lea ecx,dword ptr ss:[ebp-0x2C] //取第二个指针
01214D84 83C1 0C add ecx,0xC
01214D87 898D DCFEFFFF mov dword ptr ss:[ebp-0x124],ecx
01214D8D EB 0A jmp short TestCode.01214D99
01214D8F C785 DCFEFFFF 0>mov dword ptr ss:[ebp-0x124],0x0
01214D99 8B95 DCFEFFFF mov edx,dword ptr ss:[ebp-0x124]
01214D9F 8955 B0 mov dword ptr ss:[ebp-0x50],edx
01214DA2 C785 E4FEFFFF 0>mov dword ptr ss:[ebp-0x11C],0x0
01214DAC 8D4D D4 lea ecx,dword ptr ss:[ebp-0x2C]
01214DAF E8 62C5FFFF call TestCode.01211316 //析构
01214DB4 8B85 E4FEFFFF mov eax,dword ptr ss:[ebp-0x11C]
01214DBA 52 push edx
01214DBB 8BCD mov ecx,ebp
01214DBD 50 push eax
01214DBE 8D15 E04D2101 lea edx,dword ptr ds:[0x1214DE0]
01214DC4 E8 EBC2FFFF call TestCode.012110B4
01214DC9 58 pop eax
01214DCA 5A pop edx
01214DCB 5F pop edi
01214DCC 5E pop esi
01214DCD 5B pop ebx
01214DCE 81C4 24010000 add esp,0x124
01214DD4 3BEC cmp ebp,esp
01214DD6 E8 E2C3FFFF call TestCode.012111BD
01214DDB 8BE5 mov esp,ebp
01214DDD 5D pop ebp
01214DDE C3 retn
CSofaBed构造函数
01211BF0 > 55 push ebp
01211BF1 8BEC mov ebp,esp
01211BF3 6A FF push -0x1
01211BF5 68 CD5A2101 push TestCode.01215ACD
01211BFA 64:A1 00000000 mov eax,dword ptr fs:[0]
01211C00 50 push eax
01211C01 81EC D8000000 sub esp,0xD8
01211C07 53 push ebx
01211C08 56 push esi
01211C09 57 push edi
01211C0A 51 push ecx
01211C0B 8DBD 1CFFFFFF lea edi,dword ptr ss:[ebp-0xE4]
01211C11 B9 36000000 mov ecx,0x36
01211C16 B8 CCCCCCCC mov eax,0xCCCCCCCC
01211C1B F3:AB rep stos dword ptr es:[edi]
01211C1D 59 pop ecx //还原this指针
01211C1E A1 04A02101 mov eax,dword ptr ds:[__security_cookie]
01211C23 33C5 xor eax,ebp
01211C25 50 push eax
01211C26 8D45 F4 lea eax,dword ptr ss:[ebp-0xC]
01211C29 64:A3 00000000 mov dword ptr fs:[0],eax
01211C2F 894D EC mov dword ptr ss:[ebp-0x14],ecx
01211C32 C785 20FFFFFF 0>mov dword ptr ss:[ebp-0xE0],0x0 //传入构造标记
01211C3C 837D 08 00 cmp dword ptr ss:[ebp+0x8],0x0 //检查参数,防止重复构造
01211C40 74 35 je short TestCode.01211C77
01211C42 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
01211C45 C740 04 AC7C210>mov dword ptr ds:[eax+0x4],offset TestCode.CSofaBed::`vbtable' //设置CSofa中的vt_offset
01211C4C 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
01211C4F C740 10 B47C210>mov dword ptr ds:[eax+0x10],offset TestCode.CSofaBed::`vbtable'//设置CBed中的vt_offset
01211C56 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
01211C59 83C1 20 add ecx,0x20 //调整this指针
01211C5C E8 D3F6FFFF call TestCode.01211334 //调用CFurniture构造
01211C61 C745 FC 0000000>mov dword ptr ss:[ebp-0x4],0x0
01211C68 8B85 20FFFFFF mov eax,dword ptr ss:[ebp-0xE0]//获取构造标志
01211C6E 83C8 01 or eax,0x1 //标志置1
01211C71 8985 20FFFFFF mov dword ptr ss:[ebp-0xE0],eax //修改标志
01211C77 6A 00 push 0x0
01211C79 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
01211C7C E8 EFF6FFFF call TestCode.01211370 //调用CSofa构造
01211C81 C745 FC 0100000>mov dword ptr ss:[ebp-0x4],0x1
01211C88 6A 00 push 0x0
01211C8A 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
01211C8D 83C1 0C add ecx,0xC //调整this指针
01211C90 E8 EFF6FFFF call TestCode.01211384 //调用CBed构造
01211C95 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
01211C98 C700 647B2101 mov dword ptr ds:[eax],offset TestCode.CSofaBed::`vftable' //CSofaBed对应CSofa的虚表指针
01211C9E 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
01211CA1 C740 0C 747B210>mov dword ptr ds:[eax+0xC],offset TestCode.CSofaBed::`vftable'//CSofaBed对应CBed的虚表指针
01211CA8 8B45 EC mov eax,dword ptr ss:[ebp-0x14] //通过this指针和vt_offset定位到祖父类虚表指针
01211CAB 8B48 04 mov ecx,dword ptr ds:[eax+0x4] //vt_offset给ecx
01211CAE 8B51 04 mov edx,dword ptr ds:[ecx+0x4] //父类虚表指针相对于vt_offset的偏移给edx
01211CB1 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
01211CB4 C74410 04 A47C2>mov dword ptr ds:[eax+edx+0x4],offset TestCode.CSofaBed::`vftable' //CSofaBed对应CFurniture的虚表指针
01211CBC 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
01211CBF C740 1C 0600000>mov dword ptr ds:[eax+0x1C],0x6
01211CC6 C745 FC FFFFFFF>mov dword ptr ss:[ebp-0x4],-0x1
01211CCD 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
01211CD0 8B4D F4 mov ecx,dword ptr ss:[ebp-0xC]
01211CD3 64:890D 0000000>mov dword ptr fs:[0],ecx
01211CDA 59 pop ecx
01211CDB 5F pop edi
01211CDC 5E pop esi
01211CDD 5B pop ebx
01211CDE 81C4 E4000000 add esp,0xE4
01211CE4 3BEC cmp ebp,esp
01211CE6 E8 D2F4FFFF call TestCode.012111BD
01211CEB 8BE5 mov esp,ebp
01211CED 5D pop ebp
01211CEE C2 0400 retn 0x4
析构代理函数
01211AD0 > 55 push ebp
01211AD1 8BEC mov ebp,esp
01211AD3 81EC CC000000 sub esp,0xCC
01211AD9 53 push ebx
01211ADA 56 push esi
01211ADB 57 push edi
01211ADC 51 push ecx
01211ADD 8DBD 34FFFFFF lea edi,dword ptr ss:[ebp-0xCC]
01211AE3 B9 33000000 mov ecx,0x33
01211AE8 B8 CCCCCCCC mov eax,0xCCCCCCCC
01211AED F3:AB rep stos dword ptr es:[edi]
01211AEF 59 pop ecx
01211AF0 894D F8 mov dword ptr ss:[ebp-0x8],ecx
01211AF3 8B4D F8 mov ecx,dword ptr ss:[ebp-0x8]
01211AF6 83C1 20 add ecx,0x20 //调整this指针
01211AF9 E8 63F8FFFF call TestCode.01211361 //调用CSofaBed的析构
01211AFE 8B4D F8 mov ecx,dword ptr ss:[ebp-0x8]
01211B01 83C1 20 add ecx,0x20
01211B04 E8 3AF8FFFF call TestCode.01211343 //调用祖父类析构
01211B09 5F pop edi
01211B0A 5E pop esi
01211B0B 5B pop ebx
01211B0C 81C4 CC000000 add esp,0xCC
01211B12 3BEC cmp ebp,esp
01211B14 E8 A4F6FFFF call TestCode.012111BD
01211B19 8BE5 mov esp,ebp
01211B1B 5D pop ebp
01211B1C C3 retn
CSofaBed的析构
01211D00 > 55 push ebp
01211D01 8BEC mov ebp,esp
01211D03 6A FF push -0x1
01211D05 68 FB5A2101 push TestCode.01215AFB
01211D0A 64:A1 00000000 mov eax,dword ptr fs:[0]
01211D10 50 push eax
01211D11 81EC CC000000 sub esp,0xCC
01211D17 53 push ebx
01211D18 56 push esi
01211D19 57 push edi
01211D1A 51 push ecx
01211D1B 8DBD 28FFFFFF lea edi,dword ptr ss:[ebp-0xD8]
01211D21 B9 33000000 mov ecx,0x33
01211D26 B8 CCCCCCCC mov eax,0xCCCCCCCC
01211D2B F3:AB rep stos dword ptr es:[edi]
01211D2D 59 pop ecx
01211D2E A1 04A02101 mov eax,dword ptr ds:[__security_cookie]
01211D33 33C5 xor eax,ebp
01211D35 50 push eax
01211D36 8D45 F4 lea eax,dword ptr ss:[ebp-0xC]
01211D39 64:A3 00000000 mov dword ptr fs:[0],eax
01211D3F 894D EC mov dword ptr ss:[ebp-0x14],ecx
01211D42 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
01211D45 C740 E0 647B210>mov dword ptr ds:[eax-0x20],offset TestCode.CSofaBed::`vftable' //设置虚表
01211D4C 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
01211D4F C740 EC 747B210>mov dword ptr ds:[eax-0x14],offset TestCode.CSofaBed::`vftable'//设置虚表
01211D56 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
01211D59 8B48 E4 mov ecx,dword ptr ds:[eax-0x1C]
01211D5C 8B51 04 mov edx,dword ptr ds:[ecx+0x4]
01211D5F 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
01211D62 C74410 E4 A47C2>mov dword ptr ds:[eax+edx-0x1C],offset TestCode.CSofaBed::`vftable' //设置虚表
01211D6A C745 FC 0000000>mov dword ptr ss:[ebp-0x4],0x0
01211D71 8BF4 mov esi,esp
01211D73 68 BC7C2101 push TestCode.01217CBC ; ASCII "virtual ~CSofaBed\n"
01211D78 FF15 20B12101 call dword ptr ds:[<&MSVCR120D.printf>] ; MSVCR120.printf
01211D7E 83C4 04 add esp,0x4
01211D81 3BF4 cmp esi,esp
01211D83 E8 35F4FFFF call TestCode.012111BD
01211D88 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
01211D8B 83E9 04 sub ecx,0x4
01211D8E E8 BFF5FFFF call TestCode.01211352 //调用析构
01211D93 C745 FC FFFFFFF>mov dword ptr ss:[ebp-0x4],-0x1
01211D9A 8B4D EC mov ecx,dword ptr ss:[ebp-0x14]
01211D9D 83E9 14 sub ecx,0x14
01211DA0 E8 DAF5FFFF call TestCode.0121137F //调用析构
01211DA5 8B4D F4 mov ecx,dword ptr ss:[ebp-0xC]
01211DA8 64:890D 0000000>mov dword ptr fs:[0],ecx
01211DAF 59 pop ecx
01211DB0 5F pop edi
01211DB1 5E pop esi
01211DB2 5B pop ebx
01211DB3 81C4 D8000000 add esp,0xD8
01211DB9 3BEC cmp ebp,esp
01211DBB E8 FDF3FFFF call TestCode.012111BD
01211DC0 8BE5 mov esp,ebp
01211DC2 5D pop ebp
01211DC3 C3 retn

由此可以看出vt_offset指向的内存地址中保存的数据为偏移数据。Vt_offset对应的数据有两项,vt_offset所属类对象的虚表指针相对于vt_offset的偏移值,父类虚表指针相对于vt_offset的偏移值。

CSofaBed的构造过程中的特别之处是在调用时要传入一个参数,这个参数是标志信息。构造过程中要先构造父类,在构造自己。

菱形结构中子类的析构函数执行流程并没有像构造函数那样使用标记来防止重复析构,而是将祖父类放在最后调用。先依次执行两个父类的析构,然后执行祖父类的析构。