C++对象模型-virtual继承

0.测试环境

gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

1.虚继承

1
2
3
4
5
6
7
8
9
10
11
12
X
/ \
virtual / \virtual
/ \
A B
\ /
\ /
\ /
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
50
51
52
53
54
55
56
57
58
59
60
class X{
public:
int i;
virtual void show()
{
cout<<"X"<<endl;
}
virtual ~X()
{
cout<<"~X";
}
};
class A:public virtual X{
public:
int j;
virtual void show()
{
cout<<"A"<<endl;
}
virtual ~A()
{
cout<<"~A";
}
};
class B:public virtual X{
public:
int d;
virtual void show()
{
cout<<"B"<<endl;
}
virtual ~B()
{
cout<<"~B";
}
};
class C:public A, public B{
public:
int k;
virtual void show()
{
cout<<"C"<<endl;
}
virtual ~C()
{
cout<<"~C";
}
};
void test()
{
B *b= new C;
b->show();
delete b;
}

1.1 test()函数对应的汇编代码如下

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
_Z4testv: //test
.LFB1041:
.cfi_startproc
pushl %ebp //ebp压栈
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp //ebp=esp
.cfi_def_cfa_register 5
pushl %ebx //ebx压栈
subl $20, %esp //esp=esp-20
.cfi_offset 3, -12
subl $12, %esp
pushl $28 //malloc的内存大小, Class C Object的大小为28
call _Znwj //malloc内存
addl $16, %esp
movl %eax, %ebx //ebx=eax, eax为malloc的内存的首地址,即C.this
subl $12, %esp
pushl %ebx //C的首地址压栈
call _ZN1CC1Ev //C::C()
addl $16, %esp
testl %ebx, %ebx //ebx&ebx
je .L36 //if ebx&ebx=0, jmp
leal 8(%ebx), %eax //eax=C.this+8
/*
|-------|<-----C.this
| A |
|-------|<-----b
| B |
|-------|
| C |
|-------|
| X |
|-------|
*/
jmp .L37
.L36:
movl $0, %eax
.L37:
movl %eax, -12(%ebp) //b=C.this+8, 即b=BastTypeB.this, 对应的代码为b=new C;
movl -12(%ebp), %eax //eax=b
movl (%eax), %eax //eax=vptr
movl (%eax), %eax //eax=vptr[0]
subl $12, %esp
pushl -12(%ebp) //b的地址压栈
call *%eax //b->show()
addl $16, %esp
cmpl $0, -12(%ebp)
je .L39
movl -12(%ebp), %eax //eax=b
movl (%eax), %eax //eax=vptr
addl $8, %eax //eax=vptr+8
movl (%eax), %eax //eax=vptr[2], virtual机制
subl $12, %esp
pushl -12(%ebp) //b压栈
call *%eax //call vptr[2], delete b
addl $16, %esp
.L39:
//略

1.2 _ZN1CC1Ev对应的汇编代码如下:

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
_ZN1CC1Ev: //C:C()
.LFB1053:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $8, %esp
movl 8(%ebp), %eax //eax=this
addl $20, %eax //base type X的首地址
/*
|-------|<-----C.this
| A |
|-------|<-----b
| B |
|-------|
| C |
Offset 20 -->|-------|<-----eax
|vptr |
|-------|
*/
jmp .L37
subl $12, %esp
pushl %eax //压栈
call _ZN1XC2Ev //X:X()
/* _ZN1XC2Ev对应的代码如下:
_ZN1XC2Ev: //X::X()
.LFB1044:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
movl $_ZTV1X+8, %edx //dex=vptr4X
movl 8(%ebp), %eax //eax=this
movl %edx, (%eax) //[this]=vptr4X
nop
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
*/
addl $16, %esp
movl $_ZTT1C+4, %edx //edx=VTT+4
movl 8(%ebp), %eax //eax=this
subl $8, %esp
pushl %edx
pushl %eax
call _ZN1AC2Ev //A::A()
/*
_ZN1AC2Ev:
.LFB1047:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
movl 12(%ebp), %eax //eax=VTT+4
movl (%eax), %edx //edx=VTT[1], edx=vptr
movl 8(%ebp), %eax //eax=this
movl %edx, (%eax) //[this]=vptr
movl 8(%ebp), %eax //eax=this
movl (%eax), %eax
subl $12, %eax //eax=vptr-12,即vtable+0获取offset to X
movl (%eax), %eax //eax=vtable[0], eax=20
movl %eax, %edx //edx=eax
movl 8(%ebp), %eax //eax=this
addl %eax, %edx //edx=this+offset2X, 即edx=this+20
movl 12(%ebp), %eax //eax=VTT+4
movl 4(%eax), %eax //eax=eax+4,即eax=VTT+8
movl %eax, (%edx) //[edx] =VTT+8,即设置X的vptr(construction vtable for A-in-C)
nop
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
*/
addl $16, %esp
movl $_ZTT1C+12, %edx //edx=VTT(VTT for C)+12
movl 8(%ebp), %eax //eax=this
addl $8, %eax //eax=eax+8,即base type B的地址
subl $8, %esp
pushl %edx //PUSH VTT+12, the address of vptr(_ZTC1C8_1B+12)
pushl %eax //PUSH B.this
call _ZN1BC2Ev //B::B()
/* 分析略
_ZN1BC2Ev:
.LFB1050:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
movl 12(%ebp), %eax
movl (%eax), %edx
movl 8(%ebp), %eax
movl %edx, (%eax)
movl 8(%ebp), %eax
movl (%eax), %eax
subl $12, %eax
movl (%eax), %eax
movl %eax, %edx
movl 8(%ebp), %eax
addl %eax, %edx
movl 12(%ebp), %eax
movl 4(%eax), %eax
movl %eax, (%edx)
nop
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
*/
addl $16, %esp
movl $_ZTV1C+12, %edx //edx=VTT+12
movl 8(%ebp), %eax //eax=this
movl %edx, (%eax) //[this]=VTT+12
movl $20, %edx //edx=20
movl 8(%ebp), %eax //eax=this
addl %edx, %eax //eax=this+20
movl $_ZTV1C+64, %edx //edx=vptr4C
movl %edx, (%eax) //[this]=vptr4C
movl $_ZTV1C+36, %edx
movl 8(%ebp), %eax //eax=this
movl %edx, 8(%eax) //[this+8]=vptr4C
nop
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc

1.3 delete b对应的汇编代码如下:

大概意思就是:delete b,就是将base type class b的地址压栈,但是编译器生成的代码会做一层转换,自动将压栈的地址-8,即将C.this压栈,然后析构,然后free