五、C++内存管理机制 —— primitives(侯捷)
侯捷 C++八部曲笔记汇总 - - - 持续更新 ! ! !
一、C++ 面向对象高级开发
1、C++面向对象高级编程(上)
2、C++面向对象高级编程(下)
二、STL 标准库和泛型编程
1、分配器、序列式容器
2、关联式容器
3、迭代器、 算法、仿函数
4、适配器、补充
三、C++ 设计模式
四、C++ 新标准
五、C++ 内存管理机制
1、primitives
2、std::allocator
3、malloc/free
4、other allocators
5、loki::allocator
六、C++ 程序的生前和死后
五、C++内存管理机制 —— 基础工具 primitives(侯捷)
- new、delete 表达式
- 基本用法
- array new、array delete
- placement new
- operator new()、operator delete()函数
- 重载 ::operator new / ::operator delete
- 重载 operator new() / operator delete()
👉 该课程能让你:从平地到万丈高楼!—— 侯捷
👉 做到:心中自有丘壑!
💪 源码之前,了无秘密!
所有内存管理的最终动作都要跑到malloc
去,所以malloc
的效率至关重要。
- 即,最高阶是使用STL中的内存分配器,而STL的内存分配器实现是通过
new
,new[]
,new()
等较为低阶的函数,但终归会到malloc
和free
这两种C函数上来! - 一般不会到最下面操作系统层次,不然程序不具有可移植性!
C++内存使用途径:
C++内存分配的基本工具如下:
🏃 四个层面使用栗子:
void* p1 = malloc(512); //512 bytes
free(p1);
complex<int>* p2 = new complex<int>; //one object
delete p2;
void* p3 = ::operator new(512); //512 bytes (低层就是调用malloc)
::operator delete(p3); //(低层就是调用free)
//以下使用C++ 标准库提供的allocators。
//GNUC2.9 (以下函数为static,定要通过object调用)
void* p4 = alloc::allocate(512);//分配512bytes
alloc::deallocate(p4, 512);
//GNUC4.9 (以下函数为non-static,定要通过object调用)
void* p5 = allocator<int>().allocate(5); //分配5个int
allocator<int>().deallocate((int*)p5, 5);//归还个数也要告诉它
new、delete 表达式
基本用法
⭐️ new
的过程分为三步:
- 分配内存(指定类大小的空间):
new
->operator new
->malloc
; - 将指针转化为对应的类类型;
- 通过指针调用构造函数。
在上图右上角operator new
的源码中:
- 当
malloc
没有成功时(一般很少出现),会调用你设定的函数new handler
, 在这里面释放可以释放的内存,然后再调用malloc
看是否成功; std::nothrow_t& _THROW0()
参数,表示这个函数不抛异常,取而代之的是返回一个空指针,用户通过判断是否为空指针来判断是否分配成功。
👉 new handler
- 当
operator new
没有能力为你分配出你所申请的memory
,会抛出一个std::bad_alloc exception
的异常。某些老编译器则是返回0
,你仍可以令编译器那么做:
new (nothrow) Foo;
- 抛出
exception
(异常)之前会先(不止一次)调用一个可由client
(用户)指定的handler
,以下是new handler
的形式和设定方法:
typedef void(*new_handler)();
new_handler set_new_handler(new_handler p) throw();
//set_new_handler 为C++提供的一个函数
这个new handler
一般做两件事情:
- 让更多的内存可用;
- 调用
abort()
或者exit()
。
举个栗子:
👉 =default,=delete
=default
:表示使用默认版本(拷贝构造函数、拷贝赋值函数、析构函数,还有operator new/new[]
、operator delete/delete[]
,如果没有写的话,编译器会给你一套编译器合成版);
=default
:表示这个函数不要。
⭐️ delete
的过程:
4. 先调用析构函数,对象本身的内存就会被释放掉,对象也就死亡了;
5. 然后释放内存:delete
-> operator delete
-> free
。
构造函数不能被直接调用,析构函数可以被直接调用!
array new、array delete
⭐️ array new
是分配一个对象数组:
- 通常容易犯得一个错误是在
delete
的时候忘记在delete
后面加[]
导致内存泄漏的问题。
正如上图所说的:
- 对于类中没有指针的类,不加
[]
可能问题不大,因为没有指针的类析构函数本来也就没有什么大的作用; - 但是,如果有指针,忘记写
[]
,那么delete
只会触发一次析构函数,delete
掉一个指针指向的内存,其他指针指向的内存就会泄露。- 如上图的
psa
析构,str2
和str3
指向的地址会发生内存泄漏(析构的顺序依编译器而定)。
- 如上图的
使用例子:
👉 没有办法直接调用构造函数设初值,只能绕个弯使用placement new
来设初值,形式如下:
//定点的new
new(tem)A(i); //tem是一个指针
下面两张图分别表示有指针和没指针的对象数组分配内存的区别:
对于分配一个对象数组,他会把数组的大小也放到存放对象数组的内存块的开头:
- 如果在
delete
内存的时候不加[]
,编译器会把他当成一个一般的指针,指向一块对象; - 它按照一块对象的方式来解析布局,但是存储的是3块,整个布局就乱掉了,就会报错!
placement new
用array new
调用的是类的默认构造函数,还需要对数组中的对象进行真正的构造,这就需要placement new
。允许我们将对象分配在已经构建的内存中。
placement new
就等同于调用构造函数。他不会进行内存分配,具体过程如下:
- 调用重载的
operator new
,和上面的形式不一样,还传入了第二个参数buf
(定点),用于返回已经分配好的内存; - 转型;
- 调用构造函数。
operator new()、operator delete()函数
- 一般在类里可重载;
- 也可以重载全局的
::operator new
/::operator delete
,但很少,不然可能影响很大!
容器自己也有一套重载:
重载 ::operator new / ::operator delete
⭐️ 重载全局的:
⭐️ 在类中重载::operator new / ::operator delete
更有用(array new
/ array delete
重载也是一样的方法):
在类里重载operator new
和operator delete
前面一般要加上static
(静态的):
- 因为在创建对象的时候,对象还没有创建成功,因此是无法通过对象来调用一般的函数;
- 所以必须是静态的,这样才能不通过对象就能调用起来。
重载 operator new() / operator delete()
⭐️ 我们可以重载class member operator new()
的前提是:
- 每一个版本的声明都必须有独特的参数列,其中第一个参数必须是
size_t
,其余参数以new
所制定的placement arguments
为初值。
出现于new (…)
小括号内的便是所谓的 placement arguments
。形式例如:
//placement new
Foo* pf = new(300, 'c') Foo;
⭐️ 我们可以重载class member operator delete()
:
- 但它们绝不会被
delete
调用; - 只有当
new
所调用的ctor
抛出exception
,才会调用这些重载版的operator delete()
。 - 它只可能这样被调用,主要用来归还未能完全创建成功的
object
所占用的memory
。
注:仅供学习参考,如有不足,欢迎指正!👍👍👍