cpp学习-第十二章-动态内存

核心简记

非动态的内存管理:由位置决定生存期,编译器自动释放
动态内存管理:自定义生存期,手动释放。

c++的动态内存管理运算符:
new,在堆中分配对象空间并返回指针,可以选择进行初始化。
delete,接受动态对象的指针,销毁对象,释放内存。

在memory头文件,标准库提供了俩种智能指针帮助管理动态对象,它依据指针生存期自动释放其指向的对象。shared_ptr共享对象,unique_ptr独占对象。

杂记:

  1. 编译器除了没有构造时生成默认构造外,还会在没有拷贝构造函数时自动生成拷贝构造函数。此外还有赋值、析构。其行为为对每个成员拷贝、赋值、析构。
  2. 默认初始化-依据位置,可能不进行初始化。值初始化-基本类型初始为0。对类类型来说都一样。
  3. 合成默认构造函数对未指定值的成员是默认初始化!因为不管且不在全局,因此数据不由elf定
  4. auto可以自动出指针,不过引用要加&

智能指针与new\delete

shared_ptr与unique_ptr都支持的操作:

1
2
3
4
5
6
7
8
shared_ptr<T> sp 空指针,指向T类型对象
unique_ptr<T> up
P 可做条件判断,有指向时为true
*p 解引用p,获得对象
p->men 解引用取成员
p.get() 返回p中保存的指针。
swap(p,q) 交换p与q的指针
p.swap(q)

使用动态内存原因:

  1. 不知道使用对象个数
  2. 不知道对象准确类型
  3. 多个对象见共享数据

shared_ptr类

智能指针也是模板,默认初始化为空指针。

shared_ptr独有的操作:

1
2
3
4
5
6
7
make_shared<T>(args) 用args初始化一个动态对象,返回其shared_ptr
shared_ptr<T>p(q) p是shared_ptr q的拷贝,会递增计数器。计数器在共享的公共区域
p=q 递减p的计数,递增q的计数。计数为0时释放对象。
p.unique() 如果计数为1,返回true,否则false
p.use_count() 返回计数

拷贝与赋值时都会记录有对少个指向相同对象,具体实现由库决定。一旦一个计数为0(所有导致递减的操作都可),它会自动释放管理的对象。
递减的操作如:sp的析构、赋值。

直接管理内存

动态分配的内存是默认初始化的,加()为值初始化,使用new还可以直接初始化、列表初始化。

1
2
3
4
int *p=new int; //默认初始化
int *p=new int(); //值初始化
int *p=new int(123); //传统构造
vector<int> *p=new vector<int>{0,1,2}; //列表初始化

因此称new为可选择初始化,不过类类型一定初始化了。

分配const的对象:

1
const int *p=new const int(123);

返回指向const的指针。
默认情况下,如果new不能分配所要求的内存空间,抛出bad_alloc异常。可以使用new(nothrow) int分配失败时返回空指针。

delete接受指针,销毁对象释放内存。指针必须是new分配的或者空指针,可释放const指针,非new或二次释放都未定义。
delete之后指针内容不变。
手动管理当心:

  1. 忘记delete,内存泄漏。
  2. 使用已经释放的对象
  3. 同一区域释放俩次,可能破坏空间

shared_ptr与new结合使用

可以用new返回的指针初始化智能指针,必须使用直接初始化,不能使用=构造,即不可转化。传参、返回时都要手动构造。
智能指针默认使用delete释放管理的对象。可自定义
sp的其它方法:

1
2
3
4
5
6
7
shared_ptr<T> p(q) p管理内置指针q的对象。
shared_ptr<T> p(u) 从unique_ptr接管对象所有权,u被置空。
shared_ptr<T> p(q,d) p管理内置指针q的对象。使用可调用对象d代替delete
shared_ptr<T> p(p2,d) p是p2的拷贝,使用d代替delete
p.reset() 将这个p独立出来,给予新的指针或置空,原计数会递减,当为0时释放,可传d代替delete。
p.reset(q)
p.reset(q,d)

调用get返回的指针不能delete对象,否则一定会产生二次释放。因为智能指针保证指针在就可用。

智能指针和异常

函数正常结束或者发生异常,无论如何,局部对象都会被销毁—-这部分反编译还未研究。
因此智能指针在异常发生时仍可以释放,但自己管理的指针销毁不会影响对象。

因为这种某长生存期对象绑定某对象自动生存期的行为不仅发生在动态内存管理上,某些资源如网络连接,也需要这种管理方式。因此可以使用智能指针的自定义delete来管理资源。

智能指针注意:

  1. 不使用相同的内置指针初始化/reset多个智能指针
  2. 不delste get返回的指针
  3. 不使用get返回的指针初始化/reset另一个智能指针

unique_ptr

这种智能指针某时刻只能指向一个给定的对象。
特有操作:

1
2
3
4
5
6
7
8
unique_ptr<T> u 空指针
unique_ptr<T,D> u 释放指针使用类型D的可调用对象。
unique_ptr<T,D> u(d) 传入D类型的d
u=nullptr 释放u的对象,置空u
u.release() u放弃指针的控制权,返回指针,并置空u
u.reset() 释放u所指对象,提供内置指针q就将u指向q,否则空
u.reset(q)

不可赋值、拷贝uq,但可以拷贝赋值一个快销毁的unique_ptr。编译器分辨出并实行特殊拷贝…下一章介绍
uq与sq管理删除器的方式也不同,原因在16章

weak_ptr

这是一种不控制所指对象周期的智能指针,指向sp的对象。将wp绑定sp不会改变计数

1
2
3
4
5
6
7
8
9
weak_ptr<T> w 空weak
weak_ptr<T> w(sp) 与sp指向相同对象的wp
w=p p可以是sp或wp,赋值后共享对象
w.reset() w置空
w.use_count() 返回对象的sp数量
w.expired() sp数量为0返回true,否则false
w.lock() 上为true,返回空sp,否则返回一个指向其对象的sp。

主要用于不影响一个给定的sp生存期,但可以阻止用户访问一个不存在的企图。不可以直接解引用,要使用lock使用。

动态数组

new可以分配数组,allocator类是标准库实现。

new数组

分配动态数组的类必须有自己定义版本的拷贝、赋值、销毁,来管理内存。因为这些默认操作不会处理好内存
使用vector往往更好,更安全,效率也很高。

使用:

1
2
int *p=new int[数量]; //默认初始化
int *p=new int[数量](); //值初始化

返回的是元素类型的指针。可以用[],但并不是数组类型。只能默认、值初始化,或者列表
普通数组长度不可为0,但动态数组可以。

释放要用deleate[] p;按逆序销毁释放。
库提供了可以管理new数组的up:unique_ptr
不支持访问运算符,但可以使用[]访问
sp不支持动态数组,想用必须自己加删除器。

allocator

头文件memory中的实现,将内存分配与对象构造分离。

1
2
3
4
5
6
7
allocator<T> a 空对象
a.allocate(n) 分配内存,未构造,保存n个T,返回其指针。
a.deallocate(p,n) 释放p开始的n个内存,p必须是前返回的,n必须是前大小。使用前先destory
a.construct(p,args) 在p指针的位置用参数构造对象
a.destory(p) 对p指向的对象析构
同时定义了四个批量创建、拷贝函数

一个allocator对象可以多次使用的,作为分配器。用指针分辨分配出的区域是哪一个

反汇编

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
#include<memory>
#include<string>
using namespace std;
class A{
public:
string a;
A(){
a="构造函数";
}
};
int main(int argc, char const *argv[])
{
A *p=new A();
string ls=p->a;
shared_ptr<A> sp(p);//sp接管p了
ls=sp->a;
//delete
A *p2=new A();
delete p2;
return 0;
}

X86-64

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
.text:0000000000003040 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000000003040 main proc near ; DATA XREF: start+2E↑o
.text:0000000000003040
.text:0000000000003040 var_64 = dword ptr -64h
.text:0000000000003040 ptr = qword ptr -60h
.text:0000000000003040 var_58 = qword ptr -58h
.text:0000000000003040 var_50 = qword ptr -50h
.text:0000000000003040 var_48 = qword ptr -48h
.text:0000000000003040 var_40 = qword ptr -40h
.text:0000000000003040 var_38 = qword ptr -38h
.text:0000000000003040 var_30 = qword ptr -30h
.text:0000000000003040 var_28 = dword ptr -28h
.text:0000000000003040 var_24 = dword ptr -24h
.text:0000000000003040 var_20 = byte ptr -20h
.text:0000000000003040 var_10 = byte ptr -10h
.text:0000000000003040 var_8 = qword ptr -8
.text:0000000000003040
.text:0000000000003040 ; __unwind {
.text:0000000000003040 push rbp
.text:0000000000003041 mov rbp, rsp
.text:0000000000003044 sub rsp, 70h
.text:0000000000003048 mov eax, 8
.text:000000000000304D mov ecx, eax
.text:000000000000304F mov rdx, fs:28h
.text:0000000000003058 mov [rbp+var_8], rdx
.text:000000000000305C mov [rbp+var_24], 0
.text:0000000000003063 mov [rbp+var_28], edi
.text:0000000000003066 mov [rbp+var_30], rsi
//调用new,传了一个参数表示需要空间大小。内部使用malloc
.text:000000000000306A mov rdi, rcx ; unsigned __int64
.text:000000000000306D call _Znwm ; operator new(ulong)
//返回指针
.text:0000000000003072 mov rdi, rax
.text:0000000000003075 mov [rbp+var_48], rax
//调用构造函数....传入this
.text:0000000000003079 call sub_3150
//在var_10上构造string
.text:000000000000307E lea rdi, [rbp+var_10]
.text:0000000000003082 mov rax, [rbp+var_48]
.text:0000000000003086 mov [rbp+var_38], rax
.text:000000000000308A mov rsi, [rbp+var_38]
.text:000000000000308E call sub_3D00
//在var_20上构造shared_ptr
.text:0000000000003093 lea rdi, [rbp+var_20]
.text:0000000000003097 mov rsi, [rbp+var_38]
.text:000000000000309B call sub_3190
//解引用var_20
.text:00000000000030A0 lea rax, [rbp+var_20]
.text:00000000000030A4 mov rdi, rax
.text:00000000000030A7 call sub_31C0
//返回的是管理对象的指针 使用其赋值ls
.text:00000000000030AC lea rdi, [rbp+var_10]
.text:00000000000030B0 mov rsi, rax
.text:00000000000030B3 call sub_3D70
//new 8字节大小
.text:00000000000030B8 mov r8d, 8 //64位多出的通用寄存器
.text:00000000000030BE mov edi, r8d ; unsigned __int64 //e与d都是4字节
.text:00000000000030C1 mov [rbp+var_50], rax
.text:00000000000030C5 call _Znwm ; operator new(ulong)
.text:00000000000030CA mov rdi, rax
.text:00000000000030CD mov [rbp+var_58], rax
//构造函数
.text:00000000000030D1 call sub_3150
.text:00000000000030D6 mov rax, [rbp+var_58]
.text:00000000000030DA mov [rbp+var_40], rax
.text:00000000000030DE mov rcx, [rbp+var_40]
//指针==0
.text:00000000000030E2 cmp rcx, 0
.text:00000000000030E6 mov [rbp+ptr], rcx
//等于0就跳
.text:00000000000030EA jz loc_3102
//析构函数
.text:00000000000030F0 mov rdi, [rbp+ptr]
.text:00000000000030F4 call sub_31E0
//delete 内部使用free
.text:00000000000030F9 mov rdi, [rbp+ptr] ; ptr
.text:00000000000030FD call _ZdlPv ; operator delete(void *)
.text:0000000000003102
.text:0000000000003102 loc_3102: ; CODE XREF: main+AA↑j
.text:0000000000003102 mov [rbp+var_24], 0
//析构shared_ptr
.text:0000000000003109 lea rdi, [rbp+var_20]
.text:000000000000310D call sub_3200
//析构string
.text:0000000000003112 lea rdi, [rbp+var_10]
.text:0000000000003116 call sub_3800
.text:000000000000311B mov eax, [rbp+var_24]
.text:000000000000311E mov rdi, fs:28h
.text:0000000000003127 mov rcx, [rbp+var_8]
.text:000000000000312B cmp rdi, rcx
.text:000000000000312E mov [rbp+var_64], eax
.text:0000000000003131 jnz loc_3140
.text:0000000000003137 mov eax, [rbp+var_64]
.text:000000000000313A add rsp, 70h
.text:000000000000313E pop rbp
.text:000000000000313F retn
.text:0000000000003140 ; ---------------------------------------------------------------------------
.text:0000000000003140
.text:0000000000003140 loc_3140: ; CODE XREF: main+F1↑j
.text:0000000000003140 call ___stack_chk_fail
.text:0000000000003140 ; } // starts at 3040
.text:0000000000003140 main endp

ARM-64

指令简记

arm的判断条件主要由每条指令的后缀判断的,这种条件是对指令通用的。

分析

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
.text:000000000000370C ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:000000000000370C EXPORT main
.text:000000000000370C main ; DATA XREF: LOAD:0000000000000540↑o
.text:000000000000370C ; .got:main_ptr↓o
.text:000000000000370C
.text:000000000000370C var_6C = -0x6C
.text:000000000000370C var_68 = -0x68
.text:000000000000370C var_60 = -0x60
.text:000000000000370C var_58 = -0x58
.text:000000000000370C var_50 = -0x50
.text:000000000000370C var_48 = -0x48
.text:000000000000370C var_40 = -0x40
.text:000000000000370C var_38 = -0x38
.text:000000000000370C var_30 = -0x30
.text:000000000000370C var_28 = -0x28
.text:000000000000370C var_24 = -0x24
.text:000000000000370C var_20 = -0x20
.text:000000000000370C var_10 = -0x10
.text:000000000000370C var_8 = -8
.text:000000000000370C var_s0 = 0
.text:000000000000370C
.text:000000000000370C ; __unwind {
.text:000000000000370C SUB SP, SP, #0x80
.text:0000000000003710 STP X29, X30, [SP,#0x70+var_s0]
.text:0000000000003714 ADD X29, SP, #0x70
.text:0000000000003718 MOV X8, #8
.text:000000000000371C MRS X9, #3, c13, c0, #2
.text:0000000000003720 LDR X9, [X9,#0x28]
.text:0000000000003724 STUR X9, [X29,#var_8]
.text:0000000000003728 STUR WZR, [X29,#var_24]
.text:000000000000372C STUR W0, [X29,#var_28]
.text:0000000000003730 STUR X1, [X29,#var_30]
// A *p=new A();
.text:0000000000003734 MOV X0, X8 ; unsigned __int64
.text:0000000000003738 BL _Znwm ; operator new(ulong)
.text:000000000000373C STR X0, [SP,#0x70+var_48]
.text:0000000000003740 BL sub_3804
// string ls=p->a;
//调用和访问对象直接使用指针,所以在堆和栈分配对象调用方式没区别
.text:0000000000003744 SUB X0, X29, #-var_10
.text:0000000000003748 LDR X8, [SP,#0x70+var_48]
.text:000000000000374C STR X8, [SP,#0x70+var_38]
.text:0000000000003750 LDR X1, [SP,#0x70+var_38]
.text:0000000000003754 BL sub_44D4
// shared_ptr<A> sp(p);
.text:0000000000003758 SUB X0, X29, #-var_20
.text:000000000000375C LDR X1, [SP,#0x70+var_38]
.text:0000000000003760 BL sub_3840
//取sp的this
.text:0000000000003764 SUB X8, X29, #-var_20
.text:0000000000003768 MOV X0, X8
.text:000000000000376C BL sub_386C
.text:0000000000003770 SUB X8, X29, #-var_10
.text:0000000000003774 STR X0, [SP,#0x70+var_50]
//取this的a赋值string
.text:0000000000003778 MOV X0, X8
.text:000000000000377C LDR X1, [SP,#0x70+var_50]
.text:0000000000003780 BL sub_4564
// A *p2=new A();
.text:0000000000003784 MOV X8, #8
.text:0000000000003788 STR X0, [SP,#0x70+var_58]
.text:000000000000378C MOV X0, X8 ; unsigned __int64
.text:0000000000003790 BL _Znwm ; operator new(ulong)
.text:0000000000003794 STR X0, [SP,#0x70+var_60]
.text:0000000000003798 BL sub_3804
.text:000000000000379C LDR X8, [SP,#0x70+var_60]
.text:00000000000037A0 STR X8, [SP,#0x70+var_40]
.text:00000000000037A4 LDR X9, [SP,#0x70+var_40]
.text:00000000000037A8 STR X9, [SP,#0x70+var_68]
//判断指针是否为0 是0就跳
.text:00000000000037AC CBZ X9, loc_37C0
//析构
.text:00000000000037B0 LDR X0, [SP,#0x70+var_68]
.text:00000000000037B4 BL sub_3884
.text:00000000000037B8 LDR X0, [SP,#0x70+var_68] ; void *
.text:00000000000037BC BL _ZdlPv ; operator delete(void *)
.text:00000000000037C0
.text:00000000000037C0 loc_37C0 ; CODE XREF: main+A0↑j
.text:00000000000037C0 SUB X0, X29, #-var_20
.text:00000000000037C4 STUR WZR, [X29,#var_24]
.text:00000000000037C8 BL sub_38A8
.text:00000000000037CC SUB X0, X29, #-var_10
.text:00000000000037D0 BL sub_3F60
.text:00000000000037D4 LDUR W0, [X29,#var_24]
.text:00000000000037D8 MRS X30, #3, c13, c0, #2
.text:00000000000037DC LDR X30, [X30,#0x28]
.text:00000000000037E0 LDUR X8, [X29,#var_8]
.text:00000000000037E4 CMP X30, X8
.text:00000000000037E8 STR W0, [SP,#0x70+var_6C]
.text:00000000000037EC B.NE loc_3800
.text:00000000000037F0 LDR W0, [SP,#0x70+var_6C]
.text:00000000000037F4 LDP X29, X30, [SP,#0x70+var_s0]
.text:00000000000037F8 ADD SP, SP, #0x80
.text:00000000000037FC RET
.text:0000000000003800 ; ---------------------------------------------------------------------------
.text:0000000000003800
.text:0000000000003800 loc_3800 ; CODE XREF: main+E0↑j
.text:0000000000003800 BL .__stack_chk_fail
.text:0000000000003800 ; } // starts at 370C
.text:0000000000003800 ; End of function main

这里是先用new分配内存,再对返回调用构造函数。不是在new内构造。
销毁也是先对this调用析构,再使用delete释放内存。不是在delete内析构的。此外,delete前自动判断了指针是否为null。