探索C++对象模型

本文分析了在不同场景下的C++对象模型

1. 前言

1.1 测试环境

  • Linux ubuntu18arm64 4.15.0-76-generic #86-Ubuntu SMP Fri Jan 17 17:25:58 UTC 2020 aarch64 aarch64 aarch64 GNU/Linux
  • gcc version 7.4.0 (Ubuntu/Linaro 7.4.0-1ubuntu1~18.04.1)
  • glibc 2.27
  • C++11

1.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
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
class Base {
public:
Base(): m_base(1) {}

virtual ~Base() {
m_base = 0;
}

virtual void foo() {
m_base++;
}

virtual void bar() {
m_base--;
}


private:
int m_base;
};

class Base1 : public Base {
public:
Base1(): m_base1(21) {}

virtual ~Base1() {
m_base1 = 0;
}

virtual void foo() {
m_base1++;
}

private:
int m_base1;
};

class Base2 : virtual public Base {
public:
Base2(): m_base2(22) {}

virtual ~Base2() {
m_base2 = 0;
}

virtual void foo() {
m_base2++;
}

private:
int m_base2;
};


class Base3 : virtual public Base {
public:
Base3(): m_base3(23) {}

virtual ~Base3() {
m_base3 = 0;
}

virtual void foo() {
m_base3++;
}

private:
int m_base3;
};

class Derived : public Base2, public Base3 {
public:
Derived(): m_derived(3) {}

virtual ~Derived() {
m_derived = 0;
}

virtual void foo() {
m_derived++;
}

private:
int m_derived;
};

int main(int argc, char *argv[]) {

Base b, *pb = nullptr;
Base1 b1;
Base2 b2;
Base3 b3;
Derived d;

// Base and foo
b.foo();
pb = &b;
pb->foo();

// single inheritance
b1.foo();
b1.bar();
pb = &b1;
pb->foo();
pb->bar();

// virtual single inheritance
pb = &b2;
pb->foo();

pb = &b3;
pb->foo();

// virtual multiple inheritance
b2.foo();
Base2 *pb2 = &d;
pb2->foo();

b3.foo();
Base3 *pb3 = &d;
pb3->foo();

pb = &d;
pb->foo();

d.foo();
d.bar();

return 0;
}

各类的继承关系图如下

base.jpeg

2. 调试分析

2.1 普通类

普通类可以有虚函数, 也可以没有. 这里以带虚函数的Base b对象为例

2.1.1 内存分析

查看对象信息

1
2
3
4
5
6
7
8
(gdb) p /x &b
$2 = 0xfffffffff218

(gdb) p sizeof(b)
$3 = 16

(gdb) x/2xg 0xfffffffff218
0xfffffffff218: 0x0000aaaaaaabcc58 0x0000aaaa00000001

查看_vptr.Base

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
(gdb) x/8xg 0x0000aaaaaaabcc58
0xaaaaaaabcc58 <_ZTV4Base+16>: 0x0000aaaaaaaab55c 0x0000aaaaaaaab588
0xaaaaaaabcc68 <_ZTV4Base+32>: 0x0000aaaaaaaab5ac 0x0000aaaaaaaab5d4
0xaaaaaaabcc78 <_ZTI7Derived>: 0x0000fffff7fc4338 0x0000aaaaaaaabcd0
0xaaaaaaabcc88 <_ZTI7Derived+16>: 0x0000000200000002 0x0000aaaaaaabccd8

(gdb) disas 0x0000aaaaaaaab55c
Dump of assembler code for function Base::~Base():
0x0000aaaaaaaab55c <+0>: sub sp, sp, #0x10
0x0000aaaaaaaab560 <+4>: str x0, [sp, #8]
0x0000aaaaaaaab564 <+8>: adrp x0, 0xaaaaaaabc000
0x0000aaaaaaaab568 <+12>: add x1, x0, #0xc58
0x0000aaaaaaaab56c <+16>: ldr x0, [sp, #8]
0x0000aaaaaaaab570 <+20>: str x1, [x0]
0x0000aaaaaaaab574 <+24>: ldr x0, [sp, #8]
0x0000aaaaaaaab578 <+28>: str wzr, [x0, #8]
0x0000aaaaaaaab57c <+32>: nop
0x0000aaaaaaaab580 <+36>: add sp, sp, #0x10
0x0000aaaaaaaab584 <+40>: ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab588
Dump of assembler code for function Base::~Base():
0x0000aaaaaaaab588 <+0>: stp x29, x30, [sp, #-32]!
0x0000aaaaaaaab58c <+4>: mov x29, sp
0x0000aaaaaaaab590 <+8>: str x0, [x29, #24]
0x0000aaaaaaaab594 <+12>: ldr x0, [x29, #24]
0x0000aaaaaaaab598 <+16>: bl 0xaaaaaaaab55c <Base::~Base()>
0x0000aaaaaaaab59c <+20>: ldr x0, [x29, #24]
0x0000aaaaaaaab5a0 <+24>: bl 0xaaaaaaaab110 <_ZdlPv@plt>
0x0000aaaaaaaab5a4 <+28>: ldp x29, x30, [sp], #32
0x0000aaaaaaaab5a8 <+32>: ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab5ac
Dump of assembler code for function Base::foo():
0x0000aaaaaaaab5ac <+0>: sub sp, sp, #0x10
0x0000aaaaaaaab5b0 <+4>: str x0, [sp, #8]
0x0000aaaaaaaab5b4 <+8>: ldr x0, [sp, #8]
0x0000aaaaaaaab5b8 <+12>: ldr w0, [x0, #8]
0x0000aaaaaaaab5bc <+16>: add w1, w0, #0x1
0x0000aaaaaaaab5c0 <+20>: ldr x0, [sp, #8]
0x0000aaaaaaaab5c4 <+24>: str w1, [x0, #8]
0x0000aaaaaaaab5c8 <+28>: nop
0x0000aaaaaaaab5cc <+32>: add sp, sp, #0x10
0x0000aaaaaaaab5d0 <+36>: ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab5d4
Dump of assembler code for function Base::bar():
0x0000aaaaaaaab5d4 <+0>: sub sp, sp, #0x10
0x0000aaaaaaaab5d8 <+4>: str x0, [sp, #8]
0x0000aaaaaaaab5dc <+8>: ldr x0, [sp, #8]
0x0000aaaaaaaab5e0 <+12>: ldr w0, [x0, #8]
0x0000aaaaaaaab5e4 <+16>: sub w1, w0, #0x1
0x0000aaaaaaaab5e8 <+20>: ldr x0, [sp, #8]
0x0000aaaaaaaab5ec <+24>: str w1, [x0, #8]
0x0000aaaaaaaab5f0 <+28>: nop
0x0000aaaaaaaab5f4 <+32>: add sp, sp, #0x10
0x0000aaaaaaaab5f8 <+36>: ret
End of assembler dump.

2.1.2 对象模型

根据前面的分析, 对Base类(包含虚函数)的实例b, 其对象模型如下

1
2
3
4
5
6
7
8
9
10
+-----------+            
|_vptr.Base | ----> +-------------+
+-----------+ |Base::~Base()|
| m_base | +-------------+
+-----------+ |Base::~Base()|
+-------------+
| Base::foo() |
+-------------+
| Base::bar() |
+-------------+

这里vtable中有两个版本的Base::~Base()

  • 第1个Base::~Base()完成Base类的析构函数功能. 以前提到的栈对象/局部静态对象/全局对象析构时会调用该版本
  • 第2个Base::~Base()先调用第1个Base::~Base(),然后释放对象占用的内存. 堆对象析构时会调用该版本.

2.1.3 代码分析

通过实例直接调用虚函数

1
b.foo();

反汇编如下

1
2
0x0000aaaaaaaab2d8 <+84>:	add	x0, x29, #0x48
0x0000aaaaaaaab2dc <+88>: bl 0xaaaaaaaab5ac <Base::foo()>

通过指针调用虚函数

1
2
pb = &b;
pb->foo();

反汇编如下

1
2
3
4
5
6
7
8
9
10
11
0x0000aaaaaaaab2e0 <+92>:	add	x0, x29, #0x48
0x0000aaaaaaaab2e4 <+96>: str x0, [x29, #48]
0x0000aaaaaaaab2e8 <+100>: ldr x0, [x29, #48]

0x0000aaaaaaaab2ec <+104>: ldr x0, [x0]

0x0000aaaaaaaab2f0 <+108>: add x0, x0, #0x10
0x0000aaaaaaaab2f4 <+112>: ldr x1, [x0]

0x0000aaaaaaaab2f8 <+116>: ldr x0, [x29, #48]
0x0000aaaaaaaab2fc <+120>: blr x1

流程如下

  • 确定当前对象地址(x0 = x29 + 0x48)
  • 找到vptr(gcc下默认就是[x0])
  • 在vtable中找到要调用的虚函数(vtable[2])
  • 跳转到虚函数执行

2.2 单一继承

这里以Base1 b1对象为例

2.2.1 内存分析

查看对象信息

1
2
3
4
5
6
7
8
(gdb) p /x &b1
$4 = 0xfffffffff228

(gdb) p sizeof(b1)
$5 = 16

(gdb) x/2xg 0xfffffffff228
0xfffffffff228: 0x0000aaaaaaabcc28 0x0000001500000001

查看_vptr.Base

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
(gdb) x/8xg 0x0000aaaaaaabcc28
0xaaaaaaabcc28 <_ZTV5Base1+16>: 0x0000aaaaaaaab638 0x0000aaaaaaaab670
0xaaaaaaabcc38 <_ZTV5Base1+32>: 0x0000aaaaaaaab694 0x0000aaaaaaaab5d4
0xaaaaaaabcc48 <_ZTV4Base>: 0x0000000000000000 0x0000aaaaaaabcd18
0xaaaaaaabcc58 <_ZTV4Base+16>: 0x0000aaaaaaaab55c 0x0000aaaaaaaab588

(gdb) disas 0x0000aaaaaaaab638
Dump of assembler code for function Base1::~Base1():
0x0000aaaaaaaab638 <+0>: stp x29, x30, [sp, #-32]!
0x0000aaaaaaaab63c <+4>: mov x29, sp
0x0000aaaaaaaab640 <+8>: str x0, [x29, #24]
0x0000aaaaaaaab644 <+12>: adrp x0, 0xaaaaaaabc000
0x0000aaaaaaaab648 <+16>: add x1, x0, #0xc28
0x0000aaaaaaaab64c <+20>: ldr x0, [x29, #24]
0x0000aaaaaaaab650 <+24>: str x1, [x0]
0x0000aaaaaaaab654 <+28>: ldr x0, [x29, #24]
0x0000aaaaaaaab658 <+32>: str wzr, [x0, #12]
0x0000aaaaaaaab65c <+36>: ldr x0, [x29, #24]
0x0000aaaaaaaab660 <+40>: bl 0xaaaaaaaab55c <Base::~Base()>
0x0000aaaaaaaab664 <+44>: nop
0x0000aaaaaaaab668 <+48>: ldp x29, x30, [sp], #32
0x0000aaaaaaaab66c <+52>: ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab670
Dump of assembler code for function Base1::~Base1():
0x0000aaaaaaaab670 <+0>: stp x29, x30, [sp, #-32]!
0x0000aaaaaaaab674 <+4>: mov x29, sp
0x0000aaaaaaaab678 <+8>: str x0, [x29, #24]
0x0000aaaaaaaab67c <+12>: ldr x0, [x29, #24]
0x0000aaaaaaaab680 <+16>: bl 0xaaaaaaaab638 <Base1::~Base1()>
0x0000aaaaaaaab684 <+20>: ldr x0, [x29, #24]
0x0000aaaaaaaab688 <+24>: bl 0xaaaaaaaab110 <_ZdlPv@plt>
0x0000aaaaaaaab68c <+28>: ldp x29, x30, [sp], #32
0x0000aaaaaaaab690 <+32>: ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab694
Dump of assembler code for function Base1::foo():
0x0000aaaaaaaab694 <+0>: sub sp, sp, #0x10
0x0000aaaaaaaab698 <+4>: str x0, [sp, #8]
0x0000aaaaaaaab69c <+8>: ldr x0, [sp, #8]
0x0000aaaaaaaab6a0 <+12>: ldr w0, [x0, #12]
0x0000aaaaaaaab6a4 <+16>: add w1, w0, #0x1
0x0000aaaaaaaab6a8 <+20>: ldr x0, [sp, #8]
0x0000aaaaaaaab6ac <+24>: str w1, [x0, #12]
0x0000aaaaaaaab6b0 <+28>: nop
0x0000aaaaaaaab6b4 <+32>: add sp, sp, #0x10
0x0000aaaaaaaab6b8 <+36>: ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab5d4
Dump of assembler code for function Base::bar():
0x0000aaaaaaaab5d4 <+0>: sub sp, sp, #0x10
0x0000aaaaaaaab5d8 <+4>: str x0, [sp, #8]
0x0000aaaaaaaab5dc <+8>: ldr x0, [sp, #8]
0x0000aaaaaaaab5e0 <+12>: ldr w0, [x0, #8]
0x0000aaaaaaaab5e4 <+16>: sub w1, w0, #0x1
0x0000aaaaaaaab5e8 <+20>: ldr x0, [sp, #8]
0x0000aaaaaaaab5ec <+24>: str w1, [x0, #8]
0x0000aaaaaaaab5f0 <+28>: nop
0x0000aaaaaaaab5f4 <+32>: add sp, sp, #0x10
0x0000aaaaaaaab5f8 <+36>: ret
End of assembler dump.

2.2.2 对象模型

根据前面的分析,对Base1类(包含虚函数 + 单一继承)的实例b1, 其对象模型如下

1
2
3
4
5
6
7
8
9
10
+-----------+            
|_vptr.Base | ----> +---------------+
+-----------+ |Base1::~Base1()|
| m_base | +---------------+
+-----------+ |Base1::~Base1()|
| m_base1 | +---------------+
+-----------+ | Base1::foo() |
+---------------+
| Base::bar() |
+---------------+
  • 单一继承情况下, 虽然_vptr.Base包含基类名, 但实际是Base1类的vptr
  • Base1 override基类Base的foo(), 但没有override基类Base的bar(), 所以Base1虚表中的foo()是Base1::foo(), bar()是Base::bar().

2.2.3 代码分析

通过实例直接调用虚函数

1
b1.foo();

反汇编如下

1
2
3
4
5
6
7
8
   0x0000aaaaaaaab300 <+124>:	add	x0, x29, #0x58
0x0000aaaaaaaab304 <+128>: bl 0xaaaaaaaab694 <Base1::foo()>
```

**通过基类指针调用虚函数**
```cpp
pb = &b1;
pb->foo();

反汇编如下

1
2
3
4
5
6
7
8
9
10
11
0x0000aaaaaaaab310 <+140>:	add	x0, x29, #0x58
0x0000aaaaaaaab314 <+144>: str x0, [x29, #48]
0x0000aaaaaaaab318 <+148>: ldr x0, [x29, #48]

0x0000aaaaaaaab31c <+152>: ldr x0, [x0]

0x0000aaaaaaaab320 <+156>: add x0, x0, #0x10
0x0000aaaaaaaab324 <+160>: ldr x1, [x0]

0x0000aaaaaaaab328 <+164>: ldr x0, [x29, #48]
0x0000aaaaaaaab32c <+168>: blr x1

2.3 虚继承1

先看虚继承简单的情形, 这里以Base2 b2对象为例

2.3.1 内存分析

查看对象信息

1
2
3
4
5
6
7
8
9
(gdb) p /x &b2
$6 = 0xfffffffff238

(gdb) p sizeof(b2)
$7 = 32

(gdb) x/4xg 0xfffffffff238
0xfffffffff238: 0x0000aaaaaaabcba8 0x0000ffff00000016
0xfffffffff248: 0x0000aaaaaaabcbe8 0x0000ffff00000001

这里有两个vptr

  • _vptr.Base2 = 0x0000aaaaaaabcba8
  • _vptr.Base = 0x0000aaaaaaabcbe8

查看_vptr.Base2

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
(gdb) x/8xg 0x0000aaaaaaabcba8
0xaaaaaaabcba8 <_ZTV5Base2+24>: 0x0000aaaaaaaab7c4 0x0000aaaaaaaab824
0xaaaaaaabcbb8 <_ZTV5Base2+40>: 0x0000aaaaaaaab858 0x0000000000000000
0xaaaaaaabcbc8 <_ZTV5Base2+56>: 0xfffffffffffffff0 0xfffffffffffffff0
0xaaaaaaabcbd8 <_ZTV5Base2+72>: 0xfffffffffffffff0 0x0000aaaaaaabccd8

(gdb) disas 0x0000aaaaaaaab7c4
Dump of assembler code for function Base2::~Base2():
0x0000aaaaaaaab7c4 <+0>: stp x29, x30, [sp, #-32]!
0x0000aaaaaaaab7c8 <+4>: mov x29, sp
0x0000aaaaaaaab7cc <+8>: str x0, [x29, #24]
0x0000aaaaaaaab7d0 <+12>: adrp x0, 0xaaaaaaabc000
0x0000aaaaaaaab7d4 <+16>: add x1, x0, #0xba8
0x0000aaaaaaaab7d8 <+20>: ldr x0, [x29, #24]
0x0000aaaaaaaab7dc <+24>: str x1, [x0]
0x0000aaaaaaaab7e0 <+28>: ldr x0, [x29, #24]
0x0000aaaaaaaab7e4 <+32>: add x0, x0, #0x10
0x0000aaaaaaaab7e8 <+36>: adrp x1, 0xaaaaaaabc000
0x0000aaaaaaaab7ec <+40>: add x1, x1, #0xbe8
0x0000aaaaaaaab7f0 <+44>: str x1, [x0]
0x0000aaaaaaaab7f4 <+48>: ldr x0, [x29, #24]
0x0000aaaaaaaab7f8 <+52>: str wzr, [x0, #8]
0x0000aaaaaaaab7fc <+56>: ldr x0, [x29, #24]
0x0000aaaaaaaab800 <+60>: add x0, x0, #0x10
0x0000aaaaaaaab804 <+64>: bl 0xaaaaaaaab55c <Base::~Base()>
0x0000aaaaaaaab808 <+68>: nop
0x0000aaaaaaaab80c <+72>: ldp x29, x30, [sp], #32
0x0000aaaaaaaab810 <+76>: ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab824
Dump of assembler code for function Base2::~Base2():
0x0000aaaaaaaab824 <+0>: stp x29, x30, [sp, #-32]!
0x0000aaaaaaaab828 <+4>: mov x29, sp
0x0000aaaaaaaab82c <+8>: str x0, [x29, #24]
0x0000aaaaaaaab830 <+12>: ldr x0, [x29, #24]
0x0000aaaaaaaab834 <+16>: bl 0xaaaaaaaab7c4 <Base2::~Base2()>
0x0000aaaaaaaab838 <+20>: ldr x0, [x29, #24]
0x0000aaaaaaaab83c <+24>: bl 0xaaaaaaaab110 <_ZdlPv@plt>
0x0000aaaaaaaab840 <+28>: ldp x29, x30, [sp], #32
0x0000aaaaaaaab844 <+32>: ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab858
Dump of assembler code for function Base2::foo():
0x0000aaaaaaaab858 <+0>: sub sp, sp, #0x10
0x0000aaaaaaaab85c <+4>: str x0, [sp, #8]
0x0000aaaaaaaab860 <+8>: ldr x0, [sp, #8]
0x0000aaaaaaaab864 <+12>: ldr w0, [x0, #8]
0x0000aaaaaaaab868 <+16>: add w1, w0, #0x1
0x0000aaaaaaaab86c <+20>: ldr x0, [sp, #8]
0x0000aaaaaaaab870 <+24>: str w1, [x0, #8]
0x0000aaaaaaaab874 <+28>: nop
0x0000aaaaaaaab878 <+32>: add sp, sp, #0x10
0x0000aaaaaaaab87c <+36>: ret
End of assembler dump.

查看_vptr.Base

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
(gdb) x/8xg 0x0000aaaaaaabcbe8
0xaaaaaaabcbe8 <_ZTV5Base2+88>: 0x0000aaaaaaaab814 0x0000aaaaaaaab848
0xaaaaaaabcbf8 <_ZTV5Base2+104>: 0x0000aaaaaaaab880 0x0000aaaaaaaab5d4
0xaaaaaaabcc08 <_ZTT5Base2>: 0x0000aaaaaaabcba8 0x0000aaaaaaabcbe8
0xaaaaaaabcc18 <_ZTV5Base1>: 0x0000000000000000 0x0000aaaaaaabcd00

(gdb) disas 0x0000aaaaaaaab814
Dump of assembler code for function _ZTv0_n24_N5Base2D1Ev:
0x0000aaaaaaaab814 <+0>: ldr x16, [x0]
0x0000aaaaaaaab818 <+4>: ldur x17, [x16, #-24]
0x0000aaaaaaaab81c <+8>: add x0, x0, x17
0x0000aaaaaaaab820 <+12>: b 0xaaaaaaaab7c4 <Base2::~Base2()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab848
Dump of assembler code for function _ZTv0_n24_N5Base2D0Ev:
0x0000aaaaaaaab848 <+0>: ldr x16, [x0]
0x0000aaaaaaaab84c <+4>: ldur x17, [x16, #-24]
0x0000aaaaaaaab850 <+8>: add x0, x0, x17
0x0000aaaaaaaab854 <+12>: b 0xaaaaaaaab824 <Base2::~Base2()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab880
Dump of assembler code for function _ZTv0_n32_N5Base23fooEv:
0x0000aaaaaaaab880 <+0>: ldr x16, [x0]
0x0000aaaaaaaab884 <+4>: ldur x17, [x16, #-32]
0x0000aaaaaaaab888 <+8>: add x0, x0, x17
0x0000aaaaaaaab88c <+12>: b 0xaaaaaaaab858 <Base2::foo()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab5d4
Dump of assembler code for function Base::bar():
0x0000aaaaaaaab5d4 <+0>: sub sp, sp, #0x10
0x0000aaaaaaaab5d8 <+4>: str x0, [sp, #8]
0x0000aaaaaaaab5dc <+8>: ldr x0, [sp, #8]
0x0000aaaaaaaab5e0 <+12>: ldr w0, [x0, #8]
0x0000aaaaaaaab5e4 <+16>: sub w1, w0, #0x1
0x0000aaaaaaaab5e8 <+20>: ldr x0, [sp, #8]
0x0000aaaaaaaab5ec <+24>: str w1, [x0, #8]
0x0000aaaaaaaab5f0 <+28>: nop
0x0000aaaaaaaab5f4 <+32>: add sp, sp, #0x10
0x0000aaaaaaaab5f8 <+36>: ret
End of assembler dump.

几点说明:

  • 这里的_vptr.Base(0x0000aaaaaaabcbe8)并不是Base类真正的vptr(0x0000aaaaaaabcc58)
  • 前面的三个函数是编译器生成的thunk函数,用于修正对象地址(this指针由Base子对象地址切换到Base2对象地址),跳转到Base2 override的虚函数。
  • 最后一个虚函数是Base::bar(), Base2没有override, 这里直接存储其地址, this指针为Base子对象地址

2.3.2 对象模型

根据前面的分析,对Base2类(虚继承)的实例b2, 其对象模型如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+-----------+            
|_vptr.Base2| ----> +---------------+
+-----------+ |Base2::~Base2()| <------------------
| m_base2 | +---------------+ |
+-----------+ |Base2::~Base2()| <-----------------|--
|_vptr.Base | ----| +---------------+ | |
+-----------+ | | Base2::foo() | <-----------------|-|--
| m_base | | +---------------+ | | |
+-----------+ | | | |
| --> +--------------------------------+ | | |
|virtual thunk to Base2::~Base2()|--- | |
+--------------------------------+ | |
|virtual thunk to Base2::~Base2()|----- |
+--------------------------------+ |
| virtual thunk to Base2::foo() |-------
+--------------------------------+
| Base::bar() |
+--------------------------------+

2.3.3 代码分析

通过实例直接调用虚函数

1
b2.foo();

反汇编如下

1
2
0x0000aaaaaaaab390 <+268>:	add	x0, x29, #0x68
0x0000aaaaaaaab394 <+272>: bl 0xaaaaaaaab858 <Base2::foo()>

通过基类指针调用虚函数

1
2
pb = &b2;
pb->foo();

对应汇编如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
0x0000aaaaaaaab348 <+196>:	add	x0, x29, #0x68

0x0000aaaaaaaab34c <+200>: add x0, x0, #0x10
0x0000aaaaaaaab350 <+204>: str x0, [x29, #48]
0x0000aaaaaaaab354 <+208>: ldr x0, [x29, #48]

0x0000aaaaaaaab358 <+212>: ldr x0, [x0]

0x0000aaaaaaaab35c <+216>: add x0, x0, #0x10
0x0000aaaaaaaab360 <+220>: ldr x1, [x0]

0x0000aaaaaaaab364 <+224>: ldr x0, [x29, #48]
0x0000aaaaaaaab368 <+228>: blr x1

通过上面代码可以看到, 虚继承和单一继承在通过基类指针访问派生类对象过程中,
流程不同.

  • 派生类对象地址加上特定的偏移得到基类子对象地址(编译器在处理pb = &b2后, 此时的pb已经不是b2的地址了, 而是&b2 + 0x10, 指向Base子对象)
  • 根据基类子对象地址, 找到_vptr.Base
  • 根据要调用的虚函数, 在vtable找到对应项: thunk函数基类版本
  • thunk函数是一段汇编代码, 通过修正对象地址(基类子对象地址切换到派生类对象地址), 最终跳转到派生类版本执行

2.4 虚继承2

最后看看虚继承最复杂的情形, 这里以Derived d对象为例

2.4.1 内存分析

查看对象信息

1
2
3
4
5
6
7
8
9
10
(gdb) p /x &d
$10 = 0xfffffffff278

(gdb) p sizeof(d)
$11 = 48

(gdb) x/6xg 0xfffffffff278
0xfffffffff278: 0x0000aaaaaaabc950 0x0000aaaa00000016
0xfffffffff288: 0x0000aaaaaaabc980 0x0000000500000017
0xfffffffff298: 0x0000aaaaaaabc9c0 0x0000aaaa00000001

查看_vptr.Base2

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
(gdb) x/8xg 0x0000aaaaaaabc950
0xaaaaaaabc950 <_ZTV7Derived+24>: 0x0000aaaaaaaabafc 0x0000aaaaaaaabba8
0xaaaaaaabc960 <_ZTV7Derived+40>: 0x0000aaaaaaaabbe4 0x0000000000000010
0xaaaaaaabc970 <_ZTV7Derived+56>: 0xfffffffffffffff0 0x0000aaaaaaabcc78
0xaaaaaaabc980 <_ZTV7Derived+72>: 0x0000aaaaaaaabba0 0x0000aaaaaaaabbdc

(gdb) disas 0x0000aaaaaaaabafc
Dump of assembler code for function Derived::~Derived():
0x0000aaaaaaaabafc <+0>: stp x29, x30, [sp, #-32]!
0x0000aaaaaaaabb00 <+4>: mov x29, sp
0x0000aaaaaaaabb04 <+8>: str x0, [x29, #24]
0x0000aaaaaaaabb08 <+12>: adrp x0, 0xaaaaaaabc000
0x0000aaaaaaaabb0c <+16>: add x1, x0, #0x950
0x0000aaaaaaaabb10 <+20>: ldr x0, [x29, #24]
0x0000aaaaaaaabb14 <+24>: str x1, [x0]
0x0000aaaaaaaabb18 <+28>: ldr x0, [x29, #24]
0x0000aaaaaaaabb1c <+32>: add x0, x0, #0x20
0x0000aaaaaaaabb20 <+36>: adrp x1, 0xaaaaaaabc000
0x0000aaaaaaaabb24 <+40>: add x1, x1, #0x9c0
0x0000aaaaaaaabb28 <+44>: str x1, [x0]
0x0000aaaaaaaabb2c <+48>: adrp x0, 0xaaaaaaabc000
0x0000aaaaaaaabb30 <+52>: add x1, x0, #0x980
0x0000aaaaaaaabb34 <+56>: ldr x0, [x29, #24]
0x0000aaaaaaaabb38 <+60>: str x1, [x0, #16]
0x0000aaaaaaaabb3c <+64>: ldr x0, [x29, #24]
0x0000aaaaaaaabb40 <+68>: str wzr, [x0, #28]
0x0000aaaaaaaabb44 <+72>: ldr x0, [x29, #24]
0x0000aaaaaaaabb48 <+76>: add x2, x0, #0x10
0x0000aaaaaaaabb4c <+80>: adrp x0, 0xaaaaaaabc000
0x0000aaaaaaaabb50 <+84>: add x0, x0, #0x9f8
0x0000aaaaaaaabb54 <+88>: mov x1, x0
0x0000aaaaaaaabb58 <+92>: mov x0, x2
0x0000aaaaaaaabb5c <+96>: bl 0xaaaaaaaab940 <Base3::~Base3()>
0x0000aaaaaaaabb60 <+100>: ldr x2, [x29, #24]
0x0000aaaaaaaabb64 <+104>: adrp x0, 0xaaaaaaabc000
0x0000aaaaaaaabb68 <+108>: add x0, x0, #0x9e8
0x0000aaaaaaaabb6c <+112>: mov x1, x0
0x0000aaaaaaaabb70 <+116>: mov x0, x2
0x0000aaaaaaaabb74 <+120>: bl 0xaaaaaaaab76c <Base2::~Base2()>
0x0000aaaaaaaabb78 <+124>: ldr x0, [x29, #24]
0x0000aaaaaaaabb7c <+128>: add x0, x0, #0x20
0x0000aaaaaaaabb80 <+132>: bl 0xaaaaaaaab55c <Base::~Base()>
0x0000aaaaaaaabb84 <+136>: nop
0x0000aaaaaaaabb88 <+140>: ldp x29, x30, [sp], #32
0x0000aaaaaaaabb8c <+144>: ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaabba8
Dump of assembler code for function Derived::~Derived():
0x0000aaaaaaaabba8 <+0>: stp x29, x30, [sp, #-32]!
0x0000aaaaaaaabbac <+4>: mov x29, sp
0x0000aaaaaaaabbb0 <+8>: str x0, [x29, #24]
0x0000aaaaaaaabbb4 <+12>: ldr x0, [x29, #24]
0x0000aaaaaaaabbb8 <+16>: bl 0xaaaaaaaabafc <Derived::~Derived()>
0x0000aaaaaaaabbbc <+20>: ldr x0, [x29, #24]
0x0000aaaaaaaabbc0 <+24>: bl 0xaaaaaaaab110 <_ZdlPv@plt>
0x0000aaaaaaaabbc4 <+28>: ldp x29, x30, [sp], #32
0x0000aaaaaaaabbc8 <+32>: ret
End of assembler dump.

(gdb) disas 0x0000aaaaaaaabbe4
Dump of assembler code for function Derived::foo():
0x0000aaaaaaaabbe4 <+0>: sub sp, sp, #0x10
0x0000aaaaaaaabbe8 <+4>: str x0, [sp, #8]
=> 0x0000aaaaaaaabbec <+8>: ldr x0, [sp, #8]
0x0000aaaaaaaabbf0 <+12>: ldr w0, [x0, #28]
0x0000aaaaaaaabbf4 <+16>: add w1, w0, #0x1
0x0000aaaaaaaabbf8 <+20>: ldr x0, [sp, #8]
0x0000aaaaaaaabbfc <+24>: str w1, [x0, #28]
0x0000aaaaaaaabc00 <+28>: nop
0x0000aaaaaaaabc04 <+32>: add sp, sp, #0x10
0x0000aaaaaaaabc08 <+36>: ret
End of assembler dump.

Base2类是Derived派生类列表的第一个直接基类, 这里的_vptr.Base2实际是Derived类的vptr

查看_vptr.Base3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(gdb) x/8xg 0x0000aaaaaaabc980
0xaaaaaaabc980 <_ZTV7Derived+72>: 0x0000aaaaaaaabba0 0x0000aaaaaaaabbdc
0xaaaaaaabc990 <_ZTV7Derived+88>: 0x0000aaaaaaaabc1c 0x0000000000000000
0xaaaaaaabc9a0 <_ZTV7Derived+104>: 0xffffffffffffffe0 0xffffffffffffffe0
0xaaaaaaabc9b0 <_ZTV7Derived+120>: 0xffffffffffffffe0 0x0000aaaaaaabcc78

(gdb) disas 0x0000aaaaaaaabba0
Dump of assembler code for function _ZThn16_N7DerivedD1Ev:
0x0000aaaaaaaabba0 <+0>: sub x0, x0, #0x10
0x0000aaaaaaaabba4 <+4>: b 0xaaaaaaaabafc <Derived::~Derived()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaabbdc
Dump of assembler code for function _ZThn16_N7DerivedD0Ev:
0x0000aaaaaaaabbdc <+0>: sub x0, x0, #0x10
0x0000aaaaaaaabbe0 <+4>: b 0xaaaaaaaabba8 <Derived::~Derived()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaabc1c
Dump of assembler code for function _ZThn16_N7Derived3fooEv:
0x0000aaaaaaaabc1c <+0>: sub x0, x0, #0x10
0x0000aaaaaaaabc20 <+4>: b 0xaaaaaaaabbe4 <Derived::foo()>
End of assembler dump.

Base3类是Derived派生类列表的第二个直接基类, 这里的_vptr.Base3并不是Base3类的vptr.

_vptr.Base

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
(gdb) x/8xg 0x0000aaaaaaabc9c0
0xaaaaaaabc9c0 <_ZTV7Derived+136>: 0x0000aaaaaaaabb90 0x0000aaaaaaaabbcc
0xaaaaaaabc9d0 <_ZTV7Derived+152>: 0x0000aaaaaaaabc0c 0x0000aaaaaaaab5d4
0xaaaaaaabc9e0 <_ZTT7Derived>: 0x0000aaaaaaabc950 0x0000aaaaaaabca30
0xaaaaaaabc9f0 <_ZTT7Derived+16>: 0x0000aaaaaaabca70 0x0000aaaaaaabcaa8

(gdb) disas 0x0000aaaaaaaabb90
Dump of assembler code for function _ZTv0_n24_N7DerivedD1Ev:
0x0000aaaaaaaabb90 <+0>: ldr x16, [x0]
0x0000aaaaaaaabb94 <+4>: ldur x17, [x16, #-24]
0x0000aaaaaaaabb98 <+8>: add x0, x0, x17
0x0000aaaaaaaabb9c <+12>: b 0xaaaaaaaabafc <Derived::~Derived()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaabbcc
Dump of assembler code for function _ZTv0_n24_N7DerivedD0Ev:
0x0000aaaaaaaabbcc <+0>: ldr x16, [x0]
0x0000aaaaaaaabbd0 <+4>: ldur x17, [x16, #-24]
0x0000aaaaaaaabbd4 <+8>: add x0, x0, x17
0x0000aaaaaaaabbd8 <+12>: b 0xaaaaaaaabba8 <Derived::~Derived()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaabc0c
Dump of assembler code for function _ZTv0_n32_N7Derived3fooEv:
0x0000aaaaaaaabc0c <+0>: ldr x16, [x0]
0x0000aaaaaaaabc10 <+4>: ldur x17, [x16, #-32]
0x0000aaaaaaaabc14 <+8>: add x0, x0, x17
0x0000aaaaaaaabc18 <+12>: b 0xaaaaaaaabbe4 <Derived::foo()>
End of assembler dump.

(gdb) disas 0x0000aaaaaaaab5d4
Dump of assembler code for function Base::bar():
0x0000aaaaaaaab5d4 <+0>: sub sp, sp, #0x10
0x0000aaaaaaaab5d8 <+4>: str x0, [sp, #8]
0x0000aaaaaaaab5dc <+8>: ldr x0, [sp, #8]
0x0000aaaaaaaab5e0 <+12>: ldr w0, [x0, #8]
0x0000aaaaaaaab5e4 <+16>: sub w1, w0, #0x1
0x0000aaaaaaaab5e8 <+20>: ldr x0, [sp, #8]
0x0000aaaaaaaab5ec <+24>: str w1, [x0, #8]
0x0000aaaaaaaab5f0 <+28>: nop
0x0000aaaaaaaab5f4 <+32>: add sp, sp, #0x10
0x0000aaaaaaaab5f8 <+36>: ret
End of assembler dump.

2.4.2 对象模型

根据前面的分析,对Derived类(虚函数 + 虚继承)的实例d, 其对象模型如下

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
+-------------+            
| _vptr.Base2 | ----> +-------------------+
+-------------+ |Derived::~Derived()| <---------------------------
| m_base2 | +-------------------+ | |
+-------------+ |Derived::~Derived()| <--------------------|-----|--
| _vptr.Base3 | ---- +-------------------+ | | | |
+-------------+ | | Derived::foo() | <--------------------|-|---|-|--
| m_base3 | | +-------------------+ | | | | | |
+-------------+ | | | | | | |
| m_derived | ----> +----------------------------------------+ | | | | | |
+-------------+ |non-virtual thunk to Derived::~Derived()|-- | | | | |
| _vptr.Base | ---- +----------------------------------------+ | | | | |
+-------------+ | |non-virtual thunk to Derived::~Derived()|---- | | | |
| m_base | | +----------------------------------------+ | | | |
+-------------+ | | non-virtual thunk to Derived::foo() |------ | | |
| +----------------------------------------+ | | |
| | | |
----> +------------------------------------+ | | |
|virtual thunk to Derived::~Derived()|------------ | |
+------------------------------------+ | |
|virtual thunk to Derived::~Derived()|-------------- |
+------------------------------------+ |
| virtual thunk to Derived::foo() |----------------
+------------------------------------+
| Base::bar() |
+------------------------------------+

2.4.3 代码分析

通过实例直接调用虚函数

1
d.foo();

反汇编如下

1
2
=> 0x0000aaaaaaaab408 <+388>:	add	x0, x29, #0xa8
0x0000aaaaaaaab40c <+392>: bl 0xaaaaaaaabbe4 <Derived::foo()>

通过基类指针调用虚函数

通过Base2基类指针访问d对象

1
2
Base2 *pb2 = &d;
pb2->foo();

反汇编如下

1
2
3
4
5
6
7
8
9
10
11
0x0000aaaaaaaab398 <+276>:	add	x0, x29, #0xa8
0x0000aaaaaaaab39c <+280>: str x0, [x29, #56]
0x0000aaaaaaaab3a0 <+284>: ldr x0, [x29, #56]

0x0000aaaaaaaab3a4 <+288>: ldr x0, [x0]

0x0000aaaaaaaab3a8 <+292>: add x0, x0, #0x10
0x0000aaaaaaaab3ac <+296>: ldr x1, [x0]

0x0000aaaaaaaab3b0 <+300>: ldr x0, [x29, #56]
0x0000aaaaaaaab3b4 <+304>: blr x1

通过Base3基类指针访问d对象

1
2
Base3 *pb3 = &d;
pb3->foo();

反汇编如下

1
2
3
4
5
6
7
8
9
10
11
12
13
0x0000aaaaaaaab3c0 <+316>:	add	x0, x29, #0xa8

0x0000aaaaaaaab3c4 <+320>: add x0, x0, #0x10
0x0000aaaaaaaab3c8 <+324>: str x0, [x29, #64]
0x0000aaaaaaaab3cc <+328>: ldr x0, [x29, #64]

0x0000aaaaaaaab3d0 <+332>: ldr x0, [x0]

0x0000aaaaaaaab3d4 <+336>: add x0, x0, #0x10
0x0000aaaaaaaab3d8 <+340>: ldr x1, [x0]

0x0000aaaaaaaab3dc <+344>: ldr x0, [x29, #64]
0x0000aaaaaaaab3e0 <+348>: blr x1

通过Base基类指针访问d对象

1
2
pb = &d;
pb->foo();

反汇编如下

1
2
3
4
5
6
7
8
9
10
11
12
13
0x0000aaaaaaaab3e4 <+352>:	add	x0, x29, #0xa8

0x0000aaaaaaaab3e8 <+356>: add x0, x0, #0x20
0x0000aaaaaaaab3ec <+360>: str x0, [x29, #48]
0x0000aaaaaaaab3f0 <+364>: ldr x0, [x29, #48]

0x0000aaaaaaaab3f4 <+368>: ldr x0, [x0]

0x0000aaaaaaaab3f8 <+372>: add x0, x0, #0x10
0x0000aaaaaaaab3fc <+376>: ldr x1, [x0]

0x0000aaaaaaaab400 <+380>: ldr x0, [x29, #48]
0x0000aaaaaaaab404 <+384>: blr x1

3. 总结

  • 普通类的对象模型主要由vptr(虚函数) + 当前类非静态成员变量组成
  • 单一继承类的对象模型主要由vptr + 基类非静态成员变量 + 派生类非静态成员变量组成
  • 虚继承的对象模型最复杂, 主要由多个vptr + 直接继承类非静态成员变量 + 派生类非静态成员变量 + 虚基类非静态成员变量组成

程序员自我修养

程序员自我修养(ID: dumphex)

坚持技术原创,感谢您的支持!