cpp学习-第十五章-面向对象程序设计

核心简记

面向对象的核心思想是数据抽象、继承、动态绑定。
数据抽象:接口与实现分离
继承:定义相似的类型与关系建模
动态绑定:忽略类型区别、统一使用对象

c++中将要由子适应自己而改变的函数记为虚函数,子不应该改变的函数则为普通函数。

1
virtual int f();

派生类列表指明从哪些类继承而来。形式: 冒号+访问说明符+类名 逗号分隔

1
class A : public B,public C{}

虚函数必须定义,派生类不是必须定义父的全部虚函数,没定义的会名字找到父的实现。后加override表示编译器检查,必须这个成员改写了基类虚函数。

基类的引用指针可以绑定到派生类,反之不可,当使用基类的引用或指针调用虚函数时发生动态绑定,实际调用的函数由运行时对象决定。不加虚的由指针类型静态决定。不用指针的是拷贝赋值函数调用,会砍掉子的部分。

一个类控制自己可被访问的部分,继承只是在黑盒上再进行黑盒封装。不能控制父的私有部分。

定义基类与派生类

1
2
3
4
5
6
7
8
9
10
11
12
13
class A{
public:
virtual int f(int n)
{
...
}
virtual ~A()=default;
int a;
protected:
int b;
private:
int c;
}

基类应该定义虚析构函数,为了按动态类型释放对象。
希望派生类实现的函数定义为虚函数-运行时解析,不希望改变直接继承的为普通函数-编译时解析。
虚函数通过声明前加virtual关键字。可以是构造函数与静态函数之外的函数,因为这俩个函数都不是对象绑定的。
关键字virtual只能出现在类内部的声明语句之前,不能用于外部函数定义,因为其是调用方的事-使用声明查找接口,不是实现方的事。
基类的虚函数在子类中隐式的也是虚函数。

1
2
3
4
5
6
7
8
9
10
class B:public A{
public:
int f(int n) override
{
return a+b;
}
}
A *a;
B b;
a=&b;

派生类可以访问基类的公有、受保护的成员。基类的访问修饰符同样是三个,用于控制派生类的用户对派生类继承自基类的成员的访问权。
不是必须覆盖基类的虚函数,没覆盖的行为类似普通函数。默认继承virtual,可用override。

一个派生类对象含有自己的成员以及基类的对象,c++标准没有规定对象在内存的分布。
派生类到基类的转化式隐式的。

每个类控制自己的成员初始化过程,派生使用基类的构造函数初始化基类部分。都是在初始化阶段执行的。

1
2
3
class A:B{
A(a,b,c,d):B(a,b),ac(c),ad(d){}
}

执行顺序是先基类的构造函数、再自己的,顺序按类内声明的,不写就是默认初始化。
派生类可以访问基类公有与保护成员,为的是不改变原封装性。每个类负责定义各自的接口。

如果类定义了一个静态成员,则整个继承体系中只存在该成员的唯一定义,静态成员初始化必在外,也是作为全局只能有一份,声明是供人连接的。访问使用基类、派生类均可。

声明语句目的是令程序知道某个名字的存在和表示什么实体,声明派生类直接写 class A;即可,不用加基类。

1
2
3
class A{};
class B:A{};
class C:B{};

B是C的直接基类、A是C的间接基类。

防止继承的发生:类名后加final

1
class B final:A{};

B不能当基类。

智能指针也支持派生向基类转化。

表达式有俩种类型:静态类型与动态类型。
静态类型编译时已知,是声明或表达式生成的类型。动态类型则是内存中对象的类型。
如果表达式既不是引用也不是指针,则他的动态类型与静态类型一致。

不可将基类转化为派生类,即使一个基类的指针绑定在派生类对象上,也不能将基类化为派生类。编译器无法确定运行时安全,只能检查静态类型。

初始化或赋值对象时是在调用函数,此时子赋给父是执行父的赋值,会切掉子的部分。

类型规则:

  1. 从派生到基只对指针与引用有效。
  2. 基到派生不存在隐式
  3. 转化可能受限不可执行-由于访问受限

虚函数

当且仅当使用指针/引用调用虚函数时执行动态绑定,所有虚函数都必须有定义。也只有这时静态与动态类型可能不同。
对非虚函数的调用在编译时绑定,对象进行函数调用(无论是否虚)也是编译时定。
一旦声明某个函数为虚函数,则所有派生类中都是虚函数。覆盖时形参、返回类型必须完全一致,有一个列外:当返回类型是类本身的指针或引用时,返回类型可以不一致。

override标记的函数没有覆盖虚函数将报错:

1
void f()const override;

该关键字是接口的部分,放于声明。出现在形参列表(引用与const修饰符是形参表的一部分)与尾置返回之后。

final标记函数不可被覆盖

1
void f()const final;

出现在形参列表(引用与const修饰符是形参表的一部分)与尾置返回之后。

虚函数可以有默认实参,但是函数调用使用默认实参由静态类型决定-因为实参是调用时定。因此基类派生类的默认实参最好一致。

可以回避虚函数机制使用特定的版本:类似super

1
2
A *a=new B();
a->A::f();

不管对象类型,在编译时完成解析,使用类的实现。
通常在成员函数、友元中使用作用域运算符回避虚函数机制,为了在子的实现中调用父的实现。直接调用分析的是this的类型,子的一般是子会无限递归,此时可以执行父的。

抽象基类

定义纯虚函数,该函数无需定义,在函数体的位置写=0,只能出现在类内的虚函数声明语句处。

1
2
3
class A{
virtual int f()const=0;
}

不可定义有纯虚函数的类的对象,但是派生可使用其构造来构建基类部分。
可为纯虚函数提供定义,不过函数体必须在类的外部。

含有纯虚函数的类是抽象基类,不能创建抽象基类的对象。负责定义接口。子类不定义父的纯虚函数则仍是抽象基类。

派生类构造函数只能初始化他的直接基类。向上调用后向下完成。

访问控制与继承

每个类控制自己的成员初始化、成员的可访问性。
protected关键字:类用户不可见、派生类可访问。
注意:派生类的成员只能通过派生类的对象访问基类受保护的成员,而不能通过基类对象访问。即-可访问的是派生中含的基类,单独的不可。为了防止间接访问保护成员。

继承的成员可访问性受俩个因素影响:基类中的可访问性、派生列表中的访问说明符。我接下来称其为1与2
2对派生类访问直接基类没有影响,其只有1控制。
外部对基类的访问只看1,2是决定继承后的1是什么样的。
2对1不会造成权限提升,public的2使1不变,protect的2使1的pub变为pro,pre的2使1的pub与pro变为pre。

派生向基类转化的可访问性:

  1. 只有D公有的继承B时,用户代码才可使用派生向基类的转化。
  2. 不论D怎么继承B,D的成员函数与友元都能使用转化。
  3. 继承方式是公有或保护,则D的派生类可以使用转化。

对于某个节点,基类的公有成员可访问则转化可访问。

友元:
友元的关系不可传递,同样不可继承。友元只能访问指定友元关系的类,与单独声明的成员函数范围类似。
当类A将类B声明为友元时,这种友元关系只对B与A有效,A的子类不能访问B,B的子类不能访问A。
每个类控制自己的成员的被访问权限。

改变个别的访问权限:使用using。

1
2
3
4
class B:private A{
public :
using A::a;//可使protected变为public
}

使用using将直接、间接基类中的可访问成员标记出来,继承后的访问权限由所在位置访问说明符决定。
注意这个是可以提升访问权的。

class默认派生访问是private,struct是public

1
2
3
class A{};
class B:A{}; //默认private继承
struct C:A{}; //默认public继承

这俩个关键字的唯一区别就是默认成员访问说明符与默认派生访问说明符。

继承中的作用域

派生类的作用域嵌套在基类作用域之内,内访问外,访问符是单独控制。

解析名字:a.b

  1. 先在A类中查找名字b
  2. 在A的直接基类中查找名字b
  3. 继续向上。

作用域:编译时处理用,主要供编译器查找名称。

在编译时进行名字查找,对象、指针的静态类型决定对象的那些成员是可见的。只有可见的编译时才可访问使用,动态只是决定实际执行那个。

子的成员名将隐藏外层基类相同的名字。可以使用::访问

1
2
3
4
5
6
7
8
class A{int a;}
class B:A{
int a;
int f(){
int b=a; //a是B的
return A::a; //a是A的
}
}

成员都不会被消除,只是各种形式的覆盖了。

重要!!!
函数调用的解析过程:p->mem();

  1. 确定p的静态类型,因为是调用成员,所以必须是类类型
  2. 在p的静态类型对应的类中查找mem,如果找不到就从直接基类中找,直至继承链的顶端。找到就下一步
  3. 找到men后进行类型检查,参数是否正确等,确定调用是否合法。
  4. 合法时,将根据是否是虚函数生成不同代码:虚函数且通过指针/引用使用,将运行时决定。不是时将产生常规函数调用。

注意名字优先于类型检查,此时内不会重载外部,在内中找到名字后经过类型检查不合适也不会再找了。

1
2
3
4
5
6
7
8
9
10
11
12
13
class A{
int f();
virtual void f2(){}
}
class B:A{
int f(int);
void f2(int);
}
B b;
b.f(); //错误,找到但不合适
b.A::f(); //正确,强制使用作用域
A a=new B();
a.f2(); //虚调用,使用的是A的,因为a的静态类型是A,所以从A找起,找到虚函数,动态决定,动态还是继承父的(因为子没覆盖)

虚函数有重载时,可选择覆盖0或多个。使用using可以将同名的所有重载添加到作用域中。

构造函数与拷贝控制

析构函数的执行需要看对象的动态类型,因此应该是virtual的。因此总应该写上。子析构会继承父析构的虚属性

合成的构造、赋值、析构,会直接使用基类中对应的操作对基类部分进行初始化、赋值、析构。

自定义时:
默认下都是使用默认的构造函数初始化基类部分,因此必须显示使用拷贝/移动构造。

1
2
3
4
class B:A{
B(const B&b):A(b),其它成员{} //合成的也是这样的
B(B &&b):A(std::move(b)),...{}
}

派生类的赋值也必须显示:

1
2
3
4
B &B::operator=(const B &ls)
{
A::operator=(ls); //为基类赋值,合成的是带的
}

析构会隐式执行父的析构。顺序是派生类的析构先执行、之后是基类的。

当作用于构造函数时,using将产生代码:
子类名(参数):父类名(参数){}
这样来调用父构造。且不会改变访问级别。

容器与继承

容器中的类型不能是对象类型,因为会被切掉。通常放指针、智能指针。

注意 new A(b) 时会切掉b的部分,这是在运行拷贝构造。

注意:

1
2
3
4
f(A &&a)
{
ls=new A(std::move(a));
}

这里要使用move的原因是,尽管a是右值引用,但是a本身是变量,是个左值,因此要使用move来传递。
这也是我当时反汇编出没有调用移动方法的原因。

反编译

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
#include<iostream>
using namespace std;
class A{
public:
int a;
//看构造函数对虚表等的操作
A()
{
a=2;
f3(1);
}
//子重写虚方法
virtual int f(int b)
{
return 1;
}
//子重写纯虚
virtual int f2(int b)=0;
//父有实现的纯虚
virtual int f3(int b)=0;
//子不实现的虚--是不是直接写在子的虚表里和父一样的函数
virtual int f7(int b)
{
return 7;
}
//子没有的静态
int f4(int b){
return a+b;
}
//析构函数的步骤
virtual ~A(){
a=2;
}
};
int A::f3(int b)
{
return 5;
}
//默认构造是public的 自己定义按位置改变了
class B:public A{
public:
//子的成员--若覆盖了父的就看名字查找顺序
int Ba;
//子调用父构造、虚表操作
B()
{
Ba=1;
}
//子重写虚
int f(int b)
{
return 2;
}
//子重写纯虚
int f2(int b)
{
return 3;
}
//子重写纯虚
int f3(int b){
b=A::f3(1);
return 4;
}
//子独立的静态
int f5(int b)
{
return Ba+a+b;
}
//子独立的虚
virtual int f6(int b)
{
return 6;
}
~B(){
Ba=2;
}
};
B qwe;//全局的注销位置
//虚表的位置、虚表指针的位置
int main(void)
{
B b;
//父的调用-静态动态不一致
A *pa=&b;
int ls=pa->f(1);
ls=pa->f2(1);
ls=pa->f3(1);
ls=pa->f4(1);
ls=pa->f7(1);
//子的调用-静态动态一致
B *pb=&b;
ls=pb->f(1);
ls=pb->f2(1);
ls=pb->f3(1);
ls=pb->f4(1);
ls=pb->f5(1);
ls=pb->f6(1);
ls=pb->f7(1);
//对象的静态调用-一致
ls=b.f(1);
ls=b.f2(1);
ls=b.f3(1);
ls=b.f4(1);
ls=b.f5(1);
ls=b.f6(1);
ls=b.f7(1);
}

ARM-64

简单对比:

特性 ARM V8 ARM V7
指令集 64位指令集 AArch64, 并且兼容32位指令集 AArch32 32位指令集 A32 和16位指令集 T16
支持地址长度 64位 32位
通用寄存器 31个 x0-x30(64位)或者 w0-w30(32位) 15个, r0-r14 (32位)
异常模式 4层结构 EL0-EL3 2层结构vector table
NEON 默认支持 可选支持
LAPE 默认支持 可选支持
Virtualization 默认支持 可选支持
big.LITTLE 支持 支持
TrustZone 默认支持 默认支持
SIMD寄存器 32个 X 128位 32个 X 64位

ARM-v7A中常使用32位ARM指令集并且支持thumb指令集与arm的切换。
而在ARMV8中使用的是64位ARM指令集且不再有thumb指令集状态的切换了!

因此64位下的ARM是:
B:直接跳转
BL:连接返回地址并跳转
BR:直接跳转到寄存器指定
BLR:连接并跳转寄存器指定

32位下的ARM:
B:直接跳转
BL:连接跳转
BX:指令切换跳转
BLX:连接且指令切换跳转

详细的ARM64指令将再开一篇分析。

分析

main:

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
.text:0000000000008E04 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000000008E04 EXPORT main
.text:0000000000008E04 main ; DATA XREF: LOAD:0000000000000DB8↑o
.text:0000000000008E04 ; .got:main_ptr↓o
.text:0000000000008E04
.text:0000000000008E04 var_38 = -0x38
.text:0000000000008E04 var_30 = -0x30
.text:0000000000008E04 var_24 = -0x24
.text:0000000000008E04 var_20 = -0x20
.text:0000000000008E04 var_18 = -0x18
.text:0000000000008E04 var_8 = -8
.text:0000000000008E04 var_s0 = 0
.text:0000000000008E04
.text:0000000000008E04 ; __unwind {
.text:0000000000008E04 SUB SP, SP, #0x50
.text:0000000000008E08 STP X29, X30, [SP,#0x40+var_s0]
.text:0000000000008E0C ADD X29, SP, #0x40
.text:0000000000008E10 SUB X0, X29, #-var_18
.text:0000000000008E14 MRS X8, #3, c13, c0, #2
.text:0000000000008E18 LDR X8, [X8,#0x28]
.text:0000000000008E1C STUR X8, [X29,#var_8]
//构造b,b位置在var_18
.text:0000000000008E20 BL sub_8D70
.text:0000000000008E24 MOV W1, #1
.text:0000000000008E28 SUB X8, X29, #-var_18
.text:0000000000008E2C STR X8, [SP,#0x40+var_20]
.text:0000000000008E30 LDR X8, [SP,#0x40+var_20]
父调用
//取得虚表指针。
.text:0000000000008E34 LDR X0, [X8]
//取第一项
.text:0000000000008E38 LDR X0, [X0]
.text:0000000000008E3C STR X0, [SP,#0x40+var_38]
//传this、寄存器跳转 f
.text:0000000000008E40 MOV X0, X8
.text:0000000000008E44 LDR X8, [SP,#0x40+var_38]
.text:0000000000008E48 BLR X8
//调用偏移8的 f2
.text:0000000000008E4C MOV W1, #1
.text:0000000000008E50 STR W0, [SP,#0x40+var_24]
.text:0000000000008E54 LDR X8, [SP,#0x40+var_20]
.text:0000000000008E58 LDR X30, [X8]
.text:0000000000008E5C LDR X30, [X30,#8]
.text:0000000000008E60 MOV X0, X8
.text:0000000000008E64 BLR X30
//调用偏移16的 f3
.text:0000000000008E68 MOV W1, #1
.text:0000000000008E6C STR W0, [SP,#0x40+var_24]
.text:0000000000008E70 LDR X8, [SP,#0x40+var_20]
.text:0000000000008E74 LDR X30, [X8]
.text:0000000000008E78 LDR X30, [X30,#0x10]
.text:0000000000008E7C MOV X0, X8
.text:0000000000008E80 BLR X30
//子没有的普通直接调用,因为分析为普通函数不生成虚表访问 f4
.text:0000000000008E84 MOV W1, #1
.text:0000000000008E88 STR W0, [SP,#0x40+var_24]
.text:0000000000008E8C LDR X0, [SP,#0x40+var_20]
.text:0000000000008E90 BL sub_9034
//调用偏移24的 f7
.text:0000000000008E94 MOV W1, #1
.text:0000000000008E98 STR W0, [SP,#0x40+var_24]
.text:0000000000008E9C LDR X8, [SP,#0x40+var_20]
.text:0000000000008EA0 LDR X30, [X8]
.text:0000000000008EA4 LDR X30, [X30,#0x18]
.text:0000000000008EA8 MOV X0, X8
.text:0000000000008EAC BLR X30
子调用
//偏移0 f
.text:0000000000008EB0 MOV W1, #1
.text:0000000000008EB4 SUB X8, X29, #-var_18
.text:0000000000008EB8 STR W0, [SP,#0x40+var_24]
.text:0000000000008EBC STR X8, [SP,#0x40+var_30]
.text:0000000000008EC0 LDR X8, [SP,#0x40+var_30]
.text:0000000000008EC4 LDR X30, [X8]
.text:0000000000008EC8 LDR X30, [X30]
.text:0000000000008ECC MOV X0, X8
.text:0000000000008ED0 BLR X30
//偏移8 f2
.text:0000000000008ED4 MOV W1, #1
.text:0000000000008ED8 STR W0, [SP,#0x40+var_24]
.text:0000000000008EDC LDR X8, [SP,#0x40+var_30]
.text:0000000000008EE0 LDR X30, [X8]
.text:0000000000008EE4 LDR X30, [X30,#8]
.text:0000000000008EE8 MOV X0, X8
.text:0000000000008EEC BLR X30
//偏移16 f3
.text:0000000000008EF0 MOV W1, #1
.text:0000000000008EF4 STR W0, [SP,#0x40+var_24]
.text:0000000000008EF8 LDR X8, [SP,#0x40+var_30]
.text:0000000000008EFC LDR X30, [X8]
.text:0000000000008F00 LDR X30, [X30,#0x10]
.text:0000000000008F04 MOV X0, X8
.text:0000000000008F08 BLR X30
//子没有的普通,直接调用 f4
.text:0000000000008F0C MOV W1, #1
.text:0000000000008F10 STR W0, [SP,#0x40+var_24]
.text:0000000000008F14 LDR X8, [SP,#0x40+var_30]
.text:0000000000008F18 MOV X0, X8
.text:0000000000008F1C BL sub_9034
//子独立的普通,直接调用 f5
.text:0000000000008F20 MOV W1, #1
.text:0000000000008F24 STR W0, [SP,#0x40+var_24]
.text:0000000000008F28 LDR X0, [SP,#0x40+var_30]
.text:0000000000008F2C BL sub_9058
//偏移48子独立的虚 f6
.text:0000000000008F30 MOV W1, #1
.text:0000000000008F34 STR W0, [SP,#0x40+var_24]
.text:0000000000008F38 LDR X8, [SP,#0x40+var_30]
.text:0000000000008F3C LDR X30, [X8]
.text:0000000000008F40 LDR X30, [X30,#0x30]
.text:0000000000008F44 MOV X0, X8
.text:0000000000008F48 BLR X30
//偏移24
.text:0000000000008F4C MOV W1, #1
.text:0000000000008F50 STR W0, [SP,#0x40+var_24]
.text:0000000000008F54 LDR X8, [SP,#0x40+var_30]
.text:0000000000008F58 MOV X30, X8
.text:0000000000008F5C LDR X8, [X8]
.text:0000000000008F60 LDR X8, [X8,#0x18]
.text:0000000000008F64 MOV X0, X30
.text:0000000000008F68 BLR X8
//子对象的调用
//静态调用子的f
.text:0000000000008F6C SUB X8, X29, #-var_18
.text:0000000000008F70 MOV W1, #1
.text:0000000000008F74 STR W0, [SP,#0x40+var_24]
.text:0000000000008F78 MOV X0, X8
.text:0000000000008F7C BL sub_9084
//静态子的f2
.text:0000000000008F80 SUB X8, X29, #-var_18
.text:0000000000008F84 MOV W1, #1
.text:0000000000008F88 STR W0, [SP,#0x40+var_24]
.text:0000000000008F8C MOV X0, X8
.text:0000000000008F90 BL sub_90A0
//静态子的f3
.text:0000000000008F94 SUB X8, X29, #-var_18
.text:0000000000008F98 MOV W1, #1
.text:0000000000008F9C STR W0, [SP,#0x40+var_24]
.text:0000000000008FA0 MOV X0, X8
.text:0000000000008FA4 BL sub_90BC
//父的普通f4
.text:0000000000008FA8 MOV W1, #1
.text:0000000000008FAC SUB X8, X29, #-var_18
.text:0000000000008FB0 STR W0, [SP,#0x40+var_24]
.text:0000000000008FB4 MOV X0, X8
.text:0000000000008FB8 BL sub_9034
//子的普通f5
.text:0000000000008FBC SUB X8, X29, #-var_18
.text:0000000000008FC0 MOV W1, #1
.text:0000000000008FC4 STR W0, [SP,#0x40+var_24]
.text:0000000000008FC8 MOV X0, X8
.text:0000000000008FCC BL sub_9058
//直接调用子加的虚 f6
.text:0000000000008FD0 SUB X8, X29, #-var_18
.text:0000000000008FD4 MOV W1, #1
.text:0000000000008FD8 STR W0, [SP,#0x40+var_24]
.text:0000000000008FDC MOV X0, X8
.text:0000000000008FE0 BL sub_90F8
//直接调用子不实现的父虚 f7
.text:0000000000008FE4 MOV W1, #1
.text:0000000000008FE8 SUB X8, X29, #-var_18
.text:0000000000008FEC STR W0, [SP,#0x40+var_24]
.text:0000000000008FF0 MOV X0, X8
.text:0000000000008FF4 BL sub_9114
//子析构
.text:0000000000008FF8 SUB X8, X29, #-var_18
.text:0000000000008FFC STR W0, [SP,#0x40+var_24]
.text:0000000000009000 MOV X0, X8
.text:0000000000009004 BL sub_8DC0
//返回
.text:0000000000009008 MRS X8, #3, c13, c0, #2
.text:000000000000900C LDR X8, [X8,#0x28]
.text:0000000000009010 LDUR X0, [X29,#var_8]
.text:0000000000009014 CMP X8, X0
.text:0000000000009018 B.NE loc_9030
.text:000000000000901C MOV W8, WZR
.text:0000000000009020 MOV W0, W8
.text:0000000000009024 LDP X29, X30, [SP,#0x40+var_s0]
.text:0000000000009028 ADD SP, SP, #0x50
.text:000000000000902C RET
.text:0000000000009030 ; ---------------------------------------------------------------------------
.text:0000000000009030
.text:0000000000009030 loc_9030 ; CODE XREF: main+214↑j
.text:0000000000009030 BL .__stack_chk_fail
.text:0000000000009030 ; } // starts at 8E04
.text:0000000000009030 ; End of function main

B的构造

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
.text:0000000000008D70 sub_8D70 ; CODE XREF: sub_89C8+14↑p
.text:0000000000008D70 ; main+1C↓p
.text:0000000000008D70
.text:0000000000008D70 var_10 = -0x10
.text:0000000000008D70 var_8 = -8
.text:0000000000008D70 var_s0 = 0
.text:0000000000008D70
.text:0000000000008D70 ; __unwind {
.text:0000000000008D70 SUB SP, SP, #0x20
.text:0000000000008D74 STP X29, X30, [SP,#0x10+var_s0]
.text:0000000000008D78 ADD X29, SP, #0x10
.text:0000000000008D7C STR X0, [SP,#0x10+var_8]
.text:0000000000008D80 LDR X0, [SP,#0x10+var_8]
.text:0000000000008D84 MOV X1, X0
.text:0000000000008D88 STR X0, [SP,#0x10+var_10]
.text:0000000000008D8C MOV X0, X1
//父的构造
.text:0000000000008D90 BL sub_9130
.text:0000000000008D94 MOV W8, #1
//赋值对象虚表指针为子的,虚表指针在对象一开始的位置
.text:0000000000008D98 ADRP X0, #unk_625B8@PAGE
.text:0000000000008D9C ADD X0, X0, #unk_625B8@PAGEOFF
.text:0000000000008DA0 MOV X1, #0x10
.text:0000000000008DA4 ADD X0, X0, X1
.text:0000000000008DA8 LDR X1, [SP,#0x10+var_10]
.text:0000000000008DAC STR X0, [X1]
//Ba在对象偏移12处
.text:0000000000008DB0 STR W8, [X1,#0xC]
.text:0000000000008DB4 LDP X29, X30, [SP,#0x10+var_s0]
.text:0000000000008DB8 ADD SP, SP, #0x20
.text:0000000000008DBC RET
.text:0000000000008DBC ; } // starts at 8D70
.text:0000000000008DBC ; End of function sub_8D70

A的构造

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
.text:0000000000009130 sub_9130 ; CODE XREF: sub_8D70+20↑p
.text:0000000000009130
.text:0000000000009130 var_C = -0xC
.text:0000000000009130 var_8 = -8
.text:0000000000009130 var_s0 = 0
.text:0000000000009130
.text:0000000000009130 ; __unwind {
.text:0000000000009130 SUB SP, SP, #0x20
.text:0000000000009134 STP X29, X30, [SP,#0x10+var_s0]
.text:0000000000009138 ADD X29, SP, #0x10
.text:000000000000913C MOV W1, #1
.text:0000000000009140 MOV W8, #2
//将虚表指针置为父的虚表.给的时候就把位置加过了,给的是虚表的位置
.text:0000000000009144 ADRP X9, #unk_62600@PAGE
.text:0000000000009148 ADD X9, X9, #unk_62600@PAGEOFF
.text:000000000000914C MOV X10, #0x10
.text:0000000000009150 ADD X9, X9, X10
.text:0000000000009154 STR X0, [SP,#0x10+var_8]
.text:0000000000009158 LDR X10, [SP,#0x10+var_8]
.text:000000000000915C STR X9, [X10]
//a赋值,在对象的8偏移处
.text:0000000000009160 STR W8, [X10,#8]
//取出虚表指针62610
.text:0000000000009164 LDR X9, [X10]
//取出虚表的偏移0x10 62620
.text:0000000000009168 LDR X9, [X9,#0x10]
.text:000000000000916C MOV X0, X10
//调用虚表项---这里在gcc 7.3.0下为直接正常调用,没有错误。但在android的ndk编译环境下不得使用纯虚函数带定义。否则调用时直接到虚表项的纯位
//这里的调用是错误的 写时注意不要给纯虚定义。
.text:0000000000009170 BLR X9
.text:0000000000009174 STR W0, [SP,#0x10+var_C]
.text:0000000000009178 LDP X29, X30, [SP,#0x10+var_s0]
.text:000000000000917C ADD SP, SP, #0x20
.text:0000000000009180 RET
.text:0000000000009180 ; } // starts at 9130
.text:0000000000009180 ; End of function sub_9130

虚表在需要重定位的data只读段
俩个虚表:

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
.data.rel.ro:00000000000625B8 unk_625B8 DCB 0 ; DATA XREF: sub_8D70+28↑o
.data.rel.ro:00000000000625B8 ; sub_8D70+2C↑o ...
.data.rel.ro:00000000000625B9 DCB 0
.data.rel.ro:00000000000625BA DCB 0
.data.rel.ro:00000000000625BB DCB 0
.data.rel.ro:00000000000625BC DCB 0
.data.rel.ro:00000000000625BD DCB 0
.data.rel.ro:00000000000625BE DCB 0
.data.rel.ro:00000000000625BF DCB 0
.data.rel.ro:00000000000625C0 DCB 0
.data.rel.ro:00000000000625C1 DCB 0
.data.rel.ro:00000000000625C2 DCB 0
.data.rel.ro:00000000000625C3 DCB 0
.data.rel.ro:00000000000625C4 DCB 0
.data.rel.ro:00000000000625C5 DCB 0
.data.rel.ro:00000000000625C6 DCB 0
.data.rel.ro:00000000000625C7 DCB 0
.data.rel.ro:00000000000625C8 DCQ sub_9084 //返回2的 0
.data.rel.ro:00000000000625D0 DCQ sub_90A0 //返回3的 8
.data.rel.ro:00000000000625D8 DCQ sub_90BC //返回4的 16
.data.rel.ro:00000000000625E0 DCQ sub_9114 //返回7的 24
.data.rel.ro:00000000000625E8 DCQ sub_8DC0 //析构 32
.data.rel.ro:00000000000625F0 DCQ sub_9184 40 ???? 会再调用析构且delete自己
.data.rel.ro:00000000000625F8 DCQ sub_90F8 //返回6的 48 这是子添加的
.data.rel.ro:0000000000062600 unk_62600 DCB 0 ; DATA XREF: sub_9130+14↑o
.data.rel.ro:0000000000062600 ; sub_9130+18↑o ...
.data.rel.ro:0000000000062601 DCB 0
.data.rel.ro:0000000000062602 DCB 0
.data.rel.ro:0000000000062603 DCB 0
.data.rel.ro:0000000000062604 DCB 0
.data.rel.ro:0000000000062605 DCB 0
.data.rel.ro:0000000000062606 DCB 0
.data.rel.ro:0000000000062607 DCB 0
.data.rel.ro:0000000000062608 DCB 0
.data.rel.ro:0000000000062609 DCB 0
.data.rel.ro:000000000006260A DCB 0
.data.rel.ro:000000000006260B DCB 0
.data.rel.ro:000000000006260C DCB 0
.data.rel.ro:000000000006260D DCB 0
.data.rel.ro:000000000006260E DCB 0
.data.rel.ro:000000000006260F DCB 0
.data.rel.ro:0000000000062610 DCQ sub_91B4 //返回1的 0
.data.rel.ro:0000000000062618 DCQ __cxa_pure_virtual 8
.data.rel.ro:0000000000062620 DCQ __cxa_pure_virtual 16
.data.rel.ro:0000000000062628 DCQ sub_9114 //返回7的 24
.data.rel.ro:0000000000062630 DCQ sub_91D0 //析构 32
.data.rel.ro:0000000000062638 DCQ sub_9200 40 ???? 断点命令

子的f3

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
.text:00000000000090BC sub_90BC ; CODE XREF: main+1A0↑p
.text:00000000000090BC ; DATA XREF: .data.rel.ro:00000000000625D8↓o
.text:00000000000090BC
.text:00000000000090BC var_C = -0xC
.text:00000000000090BC var_8 = -8
.text:00000000000090BC var_s0 = 0
.text:00000000000090BC
.text:00000000000090BC ; __unwind {
.text:00000000000090BC SUB SP, SP, #0x20
.text:00000000000090C0 STP X29, X30, [SP,#0x10+var_s0]
.text:00000000000090C4 ADD X29, SP, #0x10
.text:00000000000090C8 MOV W8, #1
.text:00000000000090CC STR X0, [SP,#0x10+var_8]
.text:00000000000090D0 STR W1, [SP,#0x10+var_C]
//直接调用A::f3
.text:00000000000090D4 LDR X0, [SP,#0x10+var_8]
.text:00000000000090D8 MOV W1, W8
.text:00000000000090DC BL sub_8D54 //返回5
.text:00000000000090E0 MOV W8, #4
.text:00000000000090E4 STR W0, [SP,#0x10+var_C]
.text:00000000000090E8 MOV W0, W8
.text:00000000000090EC LDP X29, X30, [SP,#0x10+var_s0]
.text:00000000000090F0 ADD SP, SP, #0x20
.text:00000000000090F4 RET
.text:00000000000090F4 ; } // starts at 90BC
.text:00000000000090F4 ; End of function sub_90BC

子析构

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
.text:0000000000008DC0 sub_8DC0 ; CODE XREF: main+200↓p
.text:0000000000008DC0 ; sub_9184+18↓p
.text:0000000000008DC0 ; DATA XREF: ...
.text:0000000000008DC0
.text:0000000000008DC0 var_8 = -8
.text:0000000000008DC0 var_s0 = 0
.text:0000000000008DC0
.text:0000000000008DC0 ; __unwind {
.text:0000000000008DC0 SUB SP, SP, #0x20
.text:0000000000008DC4 STP X29, X30, [SP,#0x10+var_s0]
.text:0000000000008DC8 ADD X29, SP, #0x10
.text:0000000000008DCC MOV W8, #2
//赋值自己的虚表指针
.text:0000000000008DD0 ADRP X9, #unk_625B8@PAGE
.text:0000000000008DD4 ADD X9, X9, #unk_625B8@PAGEOFF
.text:0000000000008DD8 MOV X10, #0x10
.text:0000000000008DDC ADD X9, X9, X10
.text:0000000000008DE0 STR X0, [SP,#0x10+var_8]
.text:0000000000008DE4 LDR X10, [SP,#0x10+var_8]
.text:0000000000008DE8 STR X9, [X10]
.text:0000000000008DEC STR W8, [X10,#0xC]
.text:0000000000008DF0 MOV X0, X10
.text:0000000000008DF4 BL sub_91D0
.text:0000000000008DF8 LDP X29, X30, [SP,#0x10+var_s0]
.text:0000000000008DFC ADD SP, SP, #0x20
.text:0000000000008E00 RET
.text:0000000000008E00 ; } // starts at 8DC0
.text:0000000000008E00 ; End of function sub_8DC0

父析构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.text:00000000000091D0 sub_91D0 ; CODE XREF: sub_8DC0+34↑p
.text:00000000000091D0 ; DATA XREF: .data.rel.ro:0000000000062630↓o
.text:00000000000091D0
.text:00000000000091D0 var_8 = -8
.text:00000000000091D0
.text:00000000000091D0 ; __unwind {
.text:00000000000091D0 SUB SP, SP, #0x10
.text:00000000000091D4 MOV W8, #2
//赋值自己的虚表指针
.text:00000000000091D8 ADRP X9, #unk_62600@PAGE
.text:00000000000091DC ADD X9, X9, #unk_62600@PAGEOFF
.text:00000000000091E0 MOV X10, #0x10
.text:00000000000091E4 ADD X9, X9, X10
.text:00000000000091E8 STR X0, [SP,#0x10+var_8]
.text:00000000000091EC LDR X10, [SP,#0x10+var_8]
.text:00000000000091F0 STR X9, [X10]
.text:00000000000091F4 STR W8, [X10,#8]
.text:00000000000091F8 ADD SP, SP, #0x10
.text:00000000000091FC RET
.text:00000000000091FC ; } // starts at 91D0
.text:00000000000091FC ; End of function sub_91D0

对象内存模型:
单继承时由低到高依次:虚表指针、父成员、子成员。直接连续存的

虚表排列:
虚表内函数的顺序按类继承层次中首次声明的顺序,先父的。因此只要继承了父的虚,虚表前面就会和父的虚顺序一样,这样保证多态调用的肯定是对应的。
子类新定义的虚会在父的后面,纯虚函数使用出错函数占位置,即使有定义也不会写进去。

构造:
构造为先执行父的构造,再执行子的虚表指针赋值、初始化、函数体。在构造中都会赋予虚表指针各自类的虚表位置。
虚表指针为类的隐藏成员,位于对象的一开始。
注意ndk中调用纯虚函数的坑,即使定义了也不能使用。使用作用域静态使用可以。

析构:
先执行子的虚表指针覆盖、函数体、成员析构,最后执行父的析构。

函数调用:与p5所述函数查找过程一致。
指针调用虚函数时都被翻译为查找虚表,寄存器跳转。
指针使用普通函数、对象使用函数都是直接调用。
作用域强制调用虚与普都使用直接调用。
但是父中使用定义了的纯虚函数会有错误。在ndk下

一些解释:
对象通过虚表指针访问虚表(类的虚函数、一个类一张虚表)。
构造中和析构中都为虚表指针赋值,为的是在父的构造与析构中执行虚函数时执行的是自己的版本。
虚表在.data.rel.ro段。

ida使用一些方面:
引用的from是数据来源,表示修改这个位置的数据的代码 标记为w
to是数据去向,表示读取这个位置数据的代码 标记为r 或者引用地址o

这里ndk编译出的arm代码引用处和实际处差了一截,不过一样可以使用交叉引用查看获取虚表地址的代码,就是构造函数与析构函数了。

全局的析构还是在代理构造中用atexit注册的。

x86-64

…太多了,就看arm的吧