为什么要叫原生版本?
因为我觉得这一块是 C++ 自带的表达式 表达式里面 去调用 C语言的 CRT 库中的 malloc
和 free
但这篇 只讲自带的表达式 而不去深究 CRT 中的内存分配函数 所以只叫原生版本
常见的内存分配
分配 | 释放 | 类属 | 可否重载 |
---|---|---|---|
malloc() | free() | C 函数 | 否 |
new | delete | C++ 表达式 | 否 |
::operator new() | ::operator delete() | C++ 函数 | 可 |
allocator |
allocator |
C++ 标准库 | 自由设计搭配的容器 |
使用示例
其中 ::operator new() 和 ::operator delete()
调用了 malloc() 和 free()
1 | void *p1 = malloc(512); |
new 表达式
Object *p = new Object(1);
编译器 在内部将其转化为
1 | Object *p; |
operator new
前面说过 里面调用的是 malloc()
1 | // 来自于 vc98/crt/src/newop2.cpp |
delete 表达式
1 | Object *p = new Object(1); |
编译器 将 delete
转化为
1 | p->~Object(); // 先调用析构函数 |
operator delete
会调用 free()
来释放内存
1 | // 来自于 vc98/crt/src/delop.cpp |
array new, array delete
1 | // 调用三次构造函数 按顺序构造 |
如果 没配套使用的话 不一定会发生内存泄漏
- 对 class without ptr member 可能没影响
- 对 class with ptr member 通常有影响
先看第一种情况
cookie |
---|
Object 1 |
Object 2 |
Object 3 |
cookie
是调用 malloc()
的时候 一起带来的 记录了这块内存的大小 相当于是额外的开销
此时 这个 class 没有 ptr member 那么 delete 的时候 只会调用 一次析构函数 但是没关系 这里面没有指针成员 因此 析构完了以后 free()
会看 cookie
知道了整块内存的大小 直接将其 释放 没有发生内存泄漏
第二种情况
假设这个 class 有 ptr member 那么 delete
的时候 只调用了 一次析构函数 那另外两个 class 里面 开辟的内存 因为没有调用到析构函数 没能被成功回收 因此发生了内存泄漏
因此发生内存泄漏不是 数组本身 而是 class 中 有可能开了内存
placement new
将 object 构建在 已经分配好的内存中
1 |
|
会被编译器转换为
1 | Object *p; |
此时 调用的 operator new 为
1 | void *operator new(size_t, void *loc) { |
相当于 不分配内存 直接把已经有的内存指针 返回回去 然后在其上面调用构造函数
重载 operator new, operator delete
重载 operator new / operator delete
可分为 全局重载 和 局部重载 其中全局重载影响太广了 一般是局部重载
要注意的点是 这两个函数 一定要是 static
不然你这个类都没有实例化 怎么能调到这两个函数 但是这两个函数就是拿来实例化的 产生了驳论 不过 C++ 编译器会默认给这两个函数 加上 static
所以你写不写 没啥所谓
1 | class Foo { |
重载 placement new, placement delete
和 重载 operator new
类似
其实这真的能说是重载 placement new
吗? placement new
是在指定位置下进行构造函数
而这个 则很像 operator new
的重载了 唯一不同是参数个数不同和使用方式不同了
1 | // 第一个参数 必须是 size_t 这是为了 自动传入大小 不然没法 new |
使用方式 Foo *pf = new(300, 'c) Foo;
basic_string 使用 new(extra) 扩充申请内存量
basic_string 重载了 placement new
来多申请内存 因为 string 内部有引用计数 多申请一块内存来存放这些信息
inline static void *operator new(size_t, size_t)
new handler
当 operator new
没能力分配你要的内存时 要么 抛出 std::bad_alloc
异常 要么则是直接返回 0 这也是为什么要检查分配出来的内存 是否可用
强行要求 编译器 不抛出异常 new (nothrow) obj;
new handler 的设定方式
1 | typedef void (*new_handler)(); |
new handler 只有两种选择
- 想办法释放内存
- 调用 abort() 或 exit()