内存分配的每一层面
applications可以调用STL,里面会有allocator进行内存分配;也可以使用C++ 基本工具primitives,比如new, new[], new(), ::operator new();还可以使用更底层的malloc和free分配和释放内存。最底层的是系统调用,比如HeapAlloc,VirtualAlloc
在C中,malloc 和 free 是标准库函数,不涉及构造函数和析构函数,只是简单的内存分配和释放
int *ptr = (int *)malloc(sizeof(int));
free(ptr);
在C++中,new 和 delete 不仅仅是内存分配和释放的操作符,还会处理对象的构造和析构
四个层面的基本用法
malloc和 ::operator new()是完全一样的效果
allocator()是创建一个临时对象来调用非static函数
__GNUC__版本4.9的分配器
基本构件之一new delete expression上
由于有分配内存,因此要try catch考虑内存分配失败时如何处理
new的过程:1.分配内存 2.指针转型 3.调用构造函数
在指定内存上创建对象使用placement new:new (pointer) Type(initializer);
void* memory = operator new(sizeof(MyClass)); // 分配内存
MyClass* obj = new (memory) MyClass(/* constructor arguments */); // 在指定内存位置创建对象
void* memory = operator new[](sizeof(MyClass) * 5); // 分配数组内存
MyClass* objArray = new (memory) MyClass[5]; // 在数组内存中创建对象
_callnewh 不是 C++ 标准中的函数,而是可能是用户定义的一个函数。通常情况下,这类函数的名字以 _new_handler 结尾,用于处理内存分配失败的情况。
在 C++ 中,当 new 表达式无法分配所需的内存时,会调用用户指定的 new_handler 函数。new_handler 是一个函数指针,指向一个用户定义的函数,其原型通常为
typedef void (*new_handler)();
这个函数可以尝试释放内存、扩大内存池,或者执行其他操作来尝试解决内存不足的问题。如果 new_handler 能够成功处理内存不足的情况,返回;如果不能处理,可以选择抛出异常或者终止程序
基本构件之一new delete expression中
使用定位 new 运算符后,必须手动调用对象的析构函数来释放资源,否则可能导致内存泄漏
obj->~MyClass(); // 手动调用析构函数
operator delete(memory); // 手动释放内存
delete的动作:先调用析构函数,然后释放内存。
operator delete里调用free
基本构件之一new delete expression下
ctor和dtor直接调用的测试
Array new
cookie记录的是下面一块的长度。malloc分配的时候会额外带上一块cookie的信息,供给free释放
测试
vc6下malloc new int[10]内存布局:灰色表示具体数据,橙色是debug模式下添加的内存。上面和最下面的两个0x61(61H)是cookie,记录整体内存分配的大小。61H实际上是60H,表示内存分配的大小,后面1H意思是占用最后一位,表示内存分配出去。浅蓝色的pad表示补齐,填补到16的倍数
placement new
placement new允许我们将对象建构在已经分配好的内存中
Complex* pc = new(buf)Complex(1, 2);这句话会被编译器转换为,分别调用operator new(需要第二个参数,表示位置,这个函数只是传回这个位置,不再分配内存),指针转型,调用构造函数
重载
new是表达式,不可改变不可重载。会调用 operator new,全局(可重载但少见)或者成员函数(可重载)
容器里把构造函数和析构函数包装在 construct()和destroy(),内存分配动作allocate()和deallocate()会走入分配器allocator中处理
容器分配内存的一般途径:容器使用分配器,在这里插入图片描述
分配器调用 ::operator new 和 ::operator delete,底层可能调用 malloc 和 free:
重载全局的::operator new 和::operator delete
在一个类中重载operator new和operator delete。编译器会自动调用
通常会加static,因为调用的地方通常在创建对象的过程中,无法通过对象来调用一般函数
重载示例
重载示例
有虚函数只是把大小放大了,一个12,一个16
在GNU C++4.9版本中构造是从上到下,析构是从下到上
使用全局new,delete示例
placement new的重载第一参数必须是size_t类型,接受类的大小,会传入Foo的大小。其余的参数就是括号里的参数
平常使用的string,就是个typedef ,define basic_string。重载了operator new。每次创建时也额外分配字符串大小的内存
Per class allocator
内存池:用malloc分配一大块(内存池),然后分成小块,减少malloc的调用次数。另外减少cookie的用量。
在Screen类中引入一个指针next,它的大小是4B,用于串联链表
delete操作,把指针p回收到单向链表中,放到链表的头指针位置
左边间隔8表示每个Screen对象内存分配的大小为8B,说明每个Screen分配的时候没有cookie。
右边间隔16,表示每个Screen对象内存分配的大小为16B,这是因为对象分配的时候上下加了cookie,最上面和最下面的cookie大小共为8B
Per class allocator 2
struct AirplaneRep,由于对齐,5B会变成8B
union 是一种特殊的数据结构,可以看作同一个东西,用不同的名称/不同的角度去看待。
上述例子用指针去看待union时,只看8个字节的前4个字节
通过union借用内存块的前4个字节当作指针
delete没有free,只是把区块回收到链表,并未把内存还给操作系统,链表可能会越来越长,超过512块
Static allocator
从软件工程的角度看,上面的operator new和operator delete对于不同 类都要重载,明显不是一个好的解法,因此将allocator抽象成一个类。
具体的类进行内存分配的时候,只需要调用allocator即可
嵌入式指针embedded pointer:借用A对象所占用的内存空间中的前4个字节,这4个字节用来 链住这些空闲的内存块;但是,一旦某一块被分配出去,那么这个块的 前4个字节 就不再需要。因此类A对象的sizeof必须不小于4字节
上述例子定义一个类型obj,不放在外部,污染全局变量。struct里放了一个指针,它的大小为4个字节。这个指针的值,存着下一个内存的地址。由于这里只需要指针,所以union可以不使用。
由于上面的CHUNK设置为5,每5个对象的内存空间是连续的(间隔都是一个对象的大小),而每个大块之间是不连续的。
Macro for static allocator
把allocator的部分拿出来用宏来定义
宏是预处理指令,用于在编译过程中执行文本替换。宏通常通过 #define 关键字定义,并在代码中通过宏名称来调用。它们是一种简单的文本替换机制,可以用于创建常量、函数替代、条件编译等。
在宏定义的末尾使用反斜杠是为了告诉编译器该宏定义将在下一行继续。如果在宏定义的最后一行没有使用反斜杠,那么编译器会认为宏定义结束了
版本1:指针,版本2:embedded pointer,版本3:抽取内存的动作到单一class Allocator 版本4:alloctator通过宏抽取出来
标准库中的allocator
其中一种分配器有16条自由链表,来应对不同大小的块分配,不同的大小的类对象,分配到不同的链表中
New Handler
new handler 是一个函数指针,当 new 操作符无法分配所需的内存时,会调用与之关联的 new handler
new handler 是全局的,一旦设置,会在程序的生命周期内一直有效,直到被其他 set_new_handler 覆盖
new handler的例子