类多继承与delete继承类

几个疑问

  1. delete时怎么判断是子还是父的,大小如何确定?
  2. 多继承时的内存分布,构造、析构、调用的方式。

分析代码:

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
class A{
public:
int Aa;
virtual int fA()
{
char a[]="fA";
return 1;
}
virtual ~A()=default;
};
class B{
public:
int Ba;
virtual int fB()
{
char a[]="fB";
return 2;
}
virtual ~B()=default;
};
class C:public A ,public B{
public:
int Ca;
virtual int fC()
{
char a[]="fC";
return 3;
}
int fB()
{
char a[]="fBC";
return 2;
}
int fA()
{
char a[]="fAC";
return 1;
}
virtual ~C()=default;
};
class D:public A{
int Da;
int fA()
{
char a[]="fAD";
return 1;
}
int fD()
{
char a[]="fD";
return 1;
}
virtual ~D()=default;
};
int main(void)
{
A *pA=new A();
delete pA;
pA=new D();
delete pA;
B *pB=new C();
pB->fB();
delete pB;
pA=new C();
pA->fA();
delete pA;
C *pC=new C();
pC->fC();
delete pC;
}

反汇编

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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
.text:0000000000002EE0 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000000002EE0 EXPORT main
.text:0000000000002EE0 main ; DATA XREF: LOAD:0000000000000518↑o
.text:0000000000002EE0 ; .got:main_ptr↓o
.text:0000000000002EE0
.text:0000000000002EE0 var_90 = -0x90
.text:0000000000002EE0 var_84 = -0x84
.text:0000000000002EE0 var_80 = -0x80
.text:0000000000002EE0 var_78 = -0x78
.text:0000000000002EE0 var_6C = -0x6C
.text:0000000000002EE0 var_68 = -0x68
.text:0000000000002EE0 var_60 = -0x60
.text:0000000000002EE0 var_54 = -0x54
.text:0000000000002EE0 var_50 = -0x50
.text:0000000000002EE0 var_48 = -0x48
.text:0000000000002EE0 var_40 = -0x40
.text:0000000000002EE0 var_38 = -0x38
.text:0000000000002EE0 var_30 = -0x30
.text:0000000000002EE0 var_28 = -0x28
.text:0000000000002EE0 var_20 = -0x20
.text:0000000000002EE0 var_18 = -0x18
.text:0000000000002EE0 var_10 = -0x10
.text:0000000000002EE0 var_4 = -4
.text:0000000000002EE0 var_s0 = 0
.text:0000000000002EE0
.text:0000000000002EE0 ; __unwind {
.text:0000000000002EE0 SUB SP, SP, #0xA0
.text:0000000000002EE4 STP X29, X30, [SP,#0x90+var_s0]
.text:0000000000002EE8 ADD X29, SP, #0x90
//分配16字节 放指针于var_28
.text:0000000000002EEC MOV X0, #0x10 ; unsigned __int64
.text:0000000000002EF0 STUR WZR, [X29,#var_4]
.text:0000000000002EF4 BL _Znwm ; operator new(ulong)
//初始化为0
.text:0000000000002EF8 MOV W8, WZR
.text:0000000000002EFC MOV X2, #0x10
.text:0000000000002F00 MOV X30, X0
.text:0000000000002F04 STUR X0, [X29,#var_28]
.text:0000000000002F08 MOV X0, X30
.text:0000000000002F0C UXTB W1, W8
.text:0000000000002F10 BL .memset
//构造函数
.text:0000000000002F14 LDUR X0, [X29,#var_28]
.text:0000000000002F18 BL sub_3104
//sub_3104 A的构造
.text:0000000000003104 sub_3104 ; CODE XREF: main+38↑p
.text:0000000000003104 ; sub_312C+20↓p ...
.text:0000000000003104
.text:0000000000003104 var_8 = -8
.text:0000000000003104
.text:0000000000003104 ; __unwind {
.text:0000000000003104 SUB SP, SP, #0x10
.text:0000000000003108 ADRP X8, #unk_2B978@PAGE
.text:000000000000310C ADD X8, X8, #unk_2B978@PAGEOFF
.text:0000000000003110 MOV X9, #0x10
.text:0000000000003114 ADD X8, X8, X9
//赋予虚表指针
.text:0000000000003118 STR X0, [SP,#0x10+var_8]
.text:000000000000311C LDR X9, [SP,#0x10+var_8]
.text:0000000000003120 STR X8, [X9]
.text:0000000000003124 ADD SP, SP, #0x10
.text:0000000000003128 RET
.text:0000000000003128 ; } // starts at 3104
.text:0000000000003128 ; End of function sub_3104
//取指针置于var_10 var_30
.text:0000000000002F1C LDUR X0, [X29,#var_28]
.text:0000000000002F20 STUR X0, [X29,#var_10]
.text:0000000000002F24 LDUR X2, [X29,#var_10]
.text:0000000000002F28 STUR X2, [X29,#var_30]
//指针是null就跳
.text:0000000000002F2C CBZ X2, loc_2F44
//this
.text:0000000000002F30 LDUR X8, [X29,#var_30]
//虚表
.text:0000000000002F34 LDR X9, [X8]
//虚表偏移16的项
.text:0000000000002F38 LDR X9, [X9,#0x10]
//执行析构 调用的是代理析构,其内执行析构与delete
.text:0000000000002F3C MOV X0, X8
.text:0000000000002F40 BLR X9
.text:0000000000002F44
.text:0000000000002F44 loc_2F44 ; CODE XREF: main+4C↑j
//分配D 16字节
.text:0000000000002F44 MOV X0, #0x10 ; unsigned __int64
.text:0000000000002F48 BL _Znwm ; operator new(ulong)
//初始化
.text:0000000000002F4C MOV W8, WZR
.text:0000000000002F50 MOV X2, #0x10
.text:0000000000002F54 MOV X30, X0
.text:0000000000002F58 STUR X0, [X29,#var_38]
.text:0000000000002F5C MOV X0, X30
.text:0000000000002F60 UXTB W1, W8
.text:0000000000002F64 BL .memset
//执行构造 D的
.text:0000000000002F68 LDUR X0, [X29,#var_38]
.text:0000000000002F6C BL sub_312C
//单继承构造
.text:000000000000312C sub_312C ; CODE XREF: main+8C↑p
.text:000000000000312C
.text:000000000000312C var_10 = -0x10
.text:000000000000312C var_8 = -8
.text:000000000000312C var_s0 = 0
.text:000000000000312C
.text:000000000000312C ; __unwind {
.text:000000000000312C SUB SP, SP, #0x20
.text:0000000000003130 STP X29, X30, [SP,#0x10+var_s0]
.text:0000000000003134 ADD X29, SP, #0x10
.text:0000000000003138 STR X0, [SP,#0x10+var_8]
.text:000000000000313C LDR X0, [SP,#0x10+var_8]
.text:0000000000003140 MOV X1, X0
.text:0000000000003144 STR X0, [SP,#0x10+var_10]
.text:0000000000003148 MOV X0, X1
.text:000000000000314C BL sub_3104 //A的构造
//虚表指针
.text:0000000000003150 ADRP X0, #unk_2B9A0@PAGE
.text:0000000000003154 ADD X0, X0, #unk_2B9A0@PAGEOFF
.text:0000000000003158 MOV X1, #0x10
.text:000000000000315C ADD X0, X0, X1
.text:0000000000003160 LDR X1, [SP,#0x10+var_10]
.text:0000000000003164 STR X0, [X1]
.text:0000000000003168 LDP X29, X30, [SP,#0x10+var_s0]
.text:000000000000316C ADD SP, SP, #0x20
.text:0000000000003170 RET
.text:0000000000003170 ; } // starts at 312C
.text:0000000000003170 ; End of function sub_312C
.text:0000000000002F70 LDUR X0, [X29,#var_38]
.text:0000000000002F74 STUR X0, [X29,#var_10]
.text:0000000000002F78 LDUR X0, [X29,#var_10]
.text:0000000000002F7C STUR X0, [X29,#var_40]
.text:0000000000002F80 CBZ X0, loc_2F98
.text:0000000000002F84 LDUR X8, [X29,#var_40]
.text:0000000000002F88 LDR X9, [X8]
.text:0000000000002F8C LDR X9, [X9,#0x10]
//调用动态析构,表第三项
.text:0000000000002F90 MOV X0, X8
.text:0000000000002F94 BLR X9
.text:0000000000002F98
.text:0000000000002F98 loc_2F98 ; CODE XREF: main+A0↑j
//分配C 32字节大小 this放var_48
.text:0000000000002F98 MOV X0, #0x20 ; unsigned __int64
.text:0000000000002F9C BL _Znwm ; operator new(ulong)
.text:0000000000002FA0 MOV W8, WZR
.text:0000000000002FA4 MOV X2, #0x20
.text:0000000000002FA8 MOV X30, X0
.text:0000000000002FAC STR X0, [SP,#0x90+var_48]
.text:0000000000002FB0 MOV X0, X30
.text:0000000000002FB4 UXTB W1, W8
.text:0000000000002FB8 BL .memset
.text:0000000000002FBC LDR X0, [SP,#0x90+var_48]
.text:0000000000002FC0 BL sub_3174
//构造函数
.text:0000000000003174 sub_3174 ; CODE XREF: main+E0↑p
.text:0000000000003174 ; main+168↑p ...
.text:0000000000003174
.text:0000000000003174 var_10 = -0x10
.text:0000000000003174 var_8 = -8
.text:0000000000003174 var_s0 = 0
.text:0000000000003174
.text:0000000000003174 ; __unwind {
.text:0000000000003174 SUB SP, SP, #0x20
.text:0000000000003178 STP X29, X30, [SP,#0x10+var_s0]
.text:000000000000317C ADD X29, SP, #0x10
//对this调用A构造
.text:0000000000003180 STR X0, [SP,#0x10+var_8]
.text:0000000000003184 LDR X0, [SP,#0x10+var_8]
.text:0000000000003188 MOV X1, X0
.text:000000000000318C STR X0, [SP,#0x10+var_10]
.text:0000000000003190 MOV X0, X1
.text:0000000000003194 BL sub_3104 //A的构造
//对this偏移16字节调用B构造
.text:0000000000003198 LDR X0, [SP,#0x10+var_10]
.text:000000000000319C ADD X0, X0, #0x10
.text:00000000000031A0 BL sub_3338 //B的构造
//修改虚表指针为自己
.text:00000000000031A4 ADRP X0, #unk_2B9C8@PAGE
.text:00000000000031A8 ADD X0, X0, #unk_2B9C8@PAGEOFF
.text:00000000000031AC MOV X1, #0x48 //指向B部分
.text:00000000000031B0 ADD X1, X0, X1
.text:00000000000031B4 MOV X30, #0x10
.text:00000000000031B8 ADD X0, X0, X30
//this偏移0处修改A虚表
.text:00000000000031BC LDR X30, [SP,#0x10+var_10]
.text:00000000000031C0 STR X0, [X30]
//this偏移16字节处修改B虚表
.text:00000000000031C4 STR X1, [X30,#0x10]
.text:00000000000031C8 LDP X29, X30, [SP,#0x10+var_s0]
.text:00000000000031CC ADD SP, SP, #0x20
.text:00000000000031D0 RET
.text:00000000000031D0 ; } // starts at 3174
.text:00000000000031D0 ; End of function sub_3174
.text:0000000000002FC4 MOV X0, XZR
.text:0000000000002FC8 LDR X2, [SP,#0x90+var_48]
.text:0000000000002FCC STR X0, [SP,#0x90+var_50]
//this是0时跳
.text:0000000000002FD0 CBZ X2, loc_2FE0
//指针加0x10放于var_50
.text:0000000000002FD4 LDR X8, [SP,#0x90+var_48]
.text:0000000000002FD8 ADD X8, X8, #0x10
.text:0000000000002FDC STR X8, [SP,#0x90+var_50]
.text:0000000000002FE0
.text:0000000000002FE0 loc_2FE0 ; CODE XREF: main+F0↑j
.text:0000000000002FE0 LDR X8, [SP,#0x90+var_50]
.text:0000000000002FE4 STUR X8, [X29,#var_18]
.text:0000000000002FE8 LDUR X8, [X29,#var_18]
//取C中B的虚表第一项,调用
.text:0000000000002FEC LDR X9, [X8]
.text:0000000000002FF0 LDR X9, [X9]
.text:0000000000002FF4 MOV X0, X8
.text:0000000000002FF8 BLR X9
.text:0000000000002FFC LDUR X8, [X29,#var_18]
.text:0000000000003000 STR W0, [SP,#0x90+var_54]
.text:0000000000003004 STR X8, [SP,#0x90+var_60]
//this是0就跳
.text:0000000000003008 CBZ X8, loc_3020
//调用this的虚表偏移16字节
.text:000000000000300C LDR X8, [SP,#0x90+var_60]
.text:0000000000003010 LDR X9, [X8]
.text:0000000000003014 LDR X9, [X9,#0x10]
.text:0000000000003018 MOV X0, X8
.text:000000000000301C BLR X9
.text:0000000000003020
pA=new C();
pA->fA();
delete pA;
.text:0000000000003020 loc_3020 ; CODE XREF: main+128↑j
.text:0000000000003020 MOV X0, #0x20 ; unsigned __int64
.text:0000000000003024 BL _Znwm ; operator new(ulong)
.text:0000000000003028 MOV W8, WZR
.text:000000000000302C MOV X2, #0x20
.text:0000000000003030 MOV X30, X0
.text:0000000000003034 STR X0, [SP,#0x90+var_68]
.text:0000000000003038 MOV X0, X30
.text:000000000000303C UXTB W1, W8
.text:0000000000003040 BL .memset
.text:0000000000003044 LDR X0, [SP,#0x90+var_68]
//new对象C都是一个构造
.text:0000000000003048 BL sub_3174
//调用this虚表的第一项 fA
.text:000000000000304C LDR X0, [SP,#0x90+var_68]
.text:0000000000003050 STUR X0, [X29,#var_10]
.text:0000000000003054 LDUR X0, [X29,#var_10]
.text:0000000000003058 LDR X2, [X0]
.text:000000000000305C LDR X2, [X2]
.text:0000000000003060 BLR X2
.text:0000000000003064 LDUR X2, [X29,#var_10]
.text:0000000000003068 STR W0, [SP,#0x90+var_6C]
.text:000000000000306C STR X2, [SP,#0x90+var_78]
.text:0000000000003070 CBZ X2, loc_3088
//调用this指针偏移0x10的析构代理
.text:0000000000003074 LDR X8, [SP,#0x90+var_78]
.text:0000000000003078 LDR X9, [X8]
.text:000000000000307C LDR X9, [X9,#0x10]
.text:0000000000003080 MOV X0, X8
.text:0000000000003084 BLR X9
.text:0000000000003088
.text:0000000000003088 loc_3088 ; CODE XREF: main+190↑j
C *pC=new C();
pC->fC();
delete pC;
.text:0000000000003088 MOV X0, #0x20 ; unsigned __int64
.text:000000000000308C BL _Znwm ; operator new(ulong)
.text:0000000000003090 MOV W8, WZR
.text:0000000000003094 MOV X2, #0x20
.text:0000000000003098 MOV X30, X0
.text:000000000000309C STR X0, [SP,#0x90+var_80]
.text:00000000000030A0 MOV X0, X30
.text:00000000000030A4 UXTB W1, W8
.text:00000000000030A8 BL .memset
.text:00000000000030AC LDR X0, [SP,#0x90+var_80]
.text:00000000000030B0 BL sub_3174
.text:00000000000030B4 LDR X0, [SP,#0x90+var_80]
.text:00000000000030B8 STUR X0, [X29,#var_20]
.text:00000000000030BC LDUR X2, [X29,#var_20]
//C虚表的A部分偏移0x18,调用fC
.text:00000000000030C0 LDR X30, [X2]
.text:00000000000030C4 LDR X30, [X30,#0x18]
.text:00000000000030C8 MOV X0, X2
.text:00000000000030CC BLR X30
.text:00000000000030D0 LDUR X2, [X29,#var_20]
.text:00000000000030D4 STR W0, [SP,#0x90+var_84]
.text:00000000000030D8 STR X2, [SP,#0x90+var_90]
.text:00000000000030DC CBZ X2, loc_30F4
.text:00000000000030E0 LDR X8, [SP,#0x90+var_90]
.text:00000000000030E4 LDR X9, [X8]
.text:00000000000030E8 LDR X9, [X9,#0x10]
.text:00000000000030EC MOV X0, X8
.text:00000000000030F0 BLR X9
.text:00000000000030F4
.text:00000000000030F4 loc_30F4 ; CODE XREF: main+1FC↑j
.text:00000000000030F4 LDUR W0, [X29,#var_4]
.text:00000000000030F8 LDP X29, X30, [SP,#0x90+var_s0]
.text:00000000000030FC ADD SP, SP, #0xA0
.text:0000000000003100 RET
.text:0000000000003100 ; } // starts at 2EE0
.text:0000000000003100 ; End of function main

A的虚表:

1
2
3
4
5
6
.data.rel.ro:000000000002B978 unk_2B978 DCB 0 ; DATA XREF: sub_3104+4↑o
.data.rel.ro:000000000002B978 ; sub_3104+8↑o
...0
.data.rel.ro:000000000002B988 DCQ sub_31D4 fA
.data.rel.ro:000000000002B990 DCQ sub_3238 A的析构,没修改虚表指针
.data.rel.ro:000000000002B998 DCQ sub_3248 A的代理析构 ,先调用sub_3238在delete this

B的虚表:

1
2
3
4
5
6
.data.rel.ro:000000000002BA28 unk_2BA28 DCB 0 ; DATA XREF: sub_3338+4↑o
.data.rel.ro:000000000002BA28 ; sub_3338+8↑o
...0
.data.rel.ro:000000000002BA38 DCQ sub_3554 fB
.data.rel.ro:000000000002BA40 DCQ sub_35B8 B的析构,没改虚表指针
.data.rel.ro:000000000002BA48 DCQ sub_35C8 B的代理析构,调用sub_35B8后delete

D的虚表:

1
2
3
4
5
6
.data.rel.ro:000000000002B9A0 unk_2B9A0 DCB 0 ; DATA XREF: sub_312C+24↑o
.data.rel.ro:000000000002B9A0 ; sub_312C+28↑o
...0
.data.rel.ro:000000000002B9B0 DCQ sub_3278 fAD
.data.rel.ro:000000000002B9B8 DCQ sub_32E4 D的析构,调用了sub_3238 这次没修改虚表指针,应该是编译器认为没必要。
.data.rel.ro:000000000002B9C0 DCQ sub_3308 D的代理析构,先调用sub_32E4,再执行delete指针

C的虚表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.data.rel.ro:000000000002B9C8 unk_2B9C8 DCB 0 ; DATA XREF: sub_3174+30↑o
.data.rel.ro:000000000002B9C8 ; sub_3174+34↑o
...0
//A用虚表
.data.rel.ro:000000000002B9D8 DCQ sub_3360 fAC
.data.rel.ro:000000000002B9E0 DCQ sub_33CC C的析构,先后调用sub_35B8与sub_3238完成B、A的析构,调用B析构前this偏移了16字节
.data.rel.ro:000000000002B9E8 DCQ sub_340C C的代理析构,sub_33CC后delete
.data.rel.ro:000000000002B9F0 DCQ sub_343C fC
.data.rel.ro:000000000002B9F8 DCQ sub_34A0 fBC
...0
//B用虚表
.data.rel.ro:000000000002BA10 DCQ sub_350C 代理的fBC,先将this减16字节再调用fBC
.data.rel.ro:000000000002BA18 DCQ sub_3524 代理的析构,先将this减16字节再对this析构sub_33CC,析构中调用了B的析构sub_35B8与A的析构sub_3238
.data.rel.ro:000000000002BA20 DCQ sub_353C 代理的代理析构,减完this调用代理析构,其中调用sub_33CC再delete this

解答

非有虚构函数的类delete直接编译为先析构之后调用free。
分配时记录了大小使用指针就可以确定,因此这个大小是与指针动态绑定的,具体做法可以存数据结构或直接用指针前一部分内存记录。因此只要确定是分配时的指针就可以一口气释放。

有虚构函数的类delete则通过虚表调用代理的析构函数。该代理先执行析构再调用free。
之前分析虚表时多出的是代理析构函数,就是给delete用的,这样delete的指针变为动态调用。

多继承时的对象内存分布:高到低
C的虚表A部分指针
A的成员
C的虚表B部分指针
B的成员
C的成员

C的虚表:
A配合部分的虚表,同时有C的部分
-A的虚函数与C覆盖的虚函数
-C的析构
-C的代理析构
-C的新虚函数
-C中覆盖B的虚函数但是A没有的虚函数
B配合部分的虚表
-代理的c覆盖b的虚函数或没有代理的c没实现b的虚函数
-代理的析构
-代理代理的析构

构造函数:
构造函数先对this调用A构造,再将this偏移到B的虚表指针部分调用B构造,再将原this的A虚表指针与B虚表指针分别指向C虚表的对应A、B部分。

使用:
将C对象赋给A时,直接用this使用,因为A的对象与虚表都符合C的分配。此时与直接使用C指针使用C一样。A使用A的方法还是C重载后的方法都是对this的所以没问题。A的方法只对this的A部分,C重载的方法针对C的整个this。此时与正常继承一样。
将C对象赋给B时,编译器可以识别出静态类型C到B的转化,因此将C的指针偏移到B的虚表指针位置赋给B的指针。此时B使用B的未被重载方法是不用代理的,因为内存布局偏移后与B一样,当B使用被C重载的方法,则使用代理再调用C的重载函数,需要在代理中将this偏移回原this使得C的方法作用于整个this。同样为B的方法针对B的部分,C的方法针对整体。
其次,虚表中C覆盖B的虚函数在A部分也存在,不过被当成C中A没有的虚函数存在虚表位置。

析构函数:
delete 有虚构函数的类的指针被编译为调用虚表项,这与普通类不同。该虚表项是编译器自动生成的析构代理类,代理类先直接调用析构函数,再delete this,C的析构函数先析构成员,再移动this调用B的析构,再调用A的。
对A指针指向C对象调用析构能直接使用C覆盖版本的正确析构,对B指针指向C对象的版本则需要再进行一次代理,将指针调回来。