分配 | 释放 | 类别 | 是否可以重载 |
malloc | free | C | 否 |
new | delete | C++ 表达式(expressions) | 否 |
operator new() | operator delete() | c++ 函数 | 是 |
operator new[] | operator delete[] | c++ 函数(用于数组) | 是 |
allocator<T>::allocate | allocator<T>::deallocate | c++ 标准库 | 可以自由设计,并应用于各容器 |
一 malloc 与 free
malloc 与 free 是 C语言中用于分配内存与释放内存的两个函数,两者配对使用。
malloc 函数的函数原型为:void* malloc(unsigned int size),它根据参数指定的尺寸来分配内存块,并且返回一个void型指针,指向新分配的内存块的初始位置。如果内存分配失败(内存不足),则函数返回NULL。
free 函数原型为:void free (void* ptr),用于将 malloc 分配的内存释放掉
#include<stdio.h>
#include<malloc.h>
int main()
{
int SIZE = 10;
// 分配三个 int 内存的空间
int* ptr = (int*)malloc(sizeof (int) * SIZE);
if(ptr == NULL)
{
printf("failed allocate. \n");
exit(1);
}
// 赋值
for(int i = 0; i < SIZE; i++)
{
ptr[i] = i;
}
// 打印
for(int i = 0; i < SIZE; i++)
{
printf("%d ", ptr[i]);
}
// 释放内存
free(ptr);
return 0;
}
这里有一个问题:
我们调用 malloc 后,只返回了一个指针,那么 free 函数如何知道 malloc 分配了多大的内存, free 以指针为起点释放掉多大的内存呢?
为了解决这个问题,内存管理提供了 cookie 机制。实际上,malloc分配的内存会在内存的起始与结尾带上有 cookie。
参考:
动态内存分配(malloc)详解-CSDN博客
C++ 中malloc函数详解(转载)_c++中void* __cdecl-CSDN博客
浅谈malloc()与free() - 知乎 (zhihu.com)
malloc和free的实现原理解析 - 知乎 (zhihu.com)
C++内存管理(malloc和free中的cookie) - 知乎 (zhihu.com)
二 operator new 与 operator delete
现在有这样一个 class Complex:
#include<iostream>
class Complex
{
public:
Complex():x(0),y(0)
{
std::cout << "Complex consturctor." << std::endl;
}
~Complex()
{
std::cout << "Complex desturctor." << std::endl;
}
// 用于给单个对象分配内存
void* operator new(std::size_t size)
{
std::cout << "Foo operator new size: " << size << std::endl;
return malloc(size);
}
void operator delete(void* ptr)
{
std::cout << "Foo operator delete " << std::endl;
return free(ptr);
}
// 用于给一组对象分配内存
void* operator new[](std::size_t size)
{
std::cout << "Foo operator new[] size: " << size << std::endl;
return malloc(size);
}
void operator delete[](void* ptr)
{
std::cout << "Foo operator delete[] " << std::endl;
return free(ptr);
}
// 标准库提供的 placement new 的重载形式
void* operator new(std::size_t size, void* ptr)
{
std::cout << "Foo operator operator new(std::size_t size, void* ptr), size: " << size << std::endl;
return ptr;
}
void operator delete(void* ptr1, void* ptr2)
{
std::cout << "Foo::operator delete(void*, void*)."<< std::endl;
}
private:
int x;
int y;
};
那么在执行 new 与 delete 两个 expression 时,内部发生了什么呢?(面试常问)
// new expression
Complex* ptr = new Complex;
// 在编译器中等同于下面大括号内
{
Complex* ptr;
try {
void* mem = operator new(size); // 1. 分配内存
ptr = static_case<Complex*>(mem); // 2. 指针类型转换
ptr->Complex::Complex(); // 3. 执行构造函数,只有编译器可以这样使用
}
cache(std::bad_alloc)
// 若是分配内存阶段出错,则不再执行 构造函数
}
// delete expression
delete ptr;
// 在编译器中等同于下面大括号内
{
ptr->~Complex(); // 1. 执行析构函数
operator delete(ptr); // 2. 释放内存
}
程序验证如下:
#include"complex.h"
int main()
{
// 输出 class 大小
std::cout << "sizeof(Complex): " << sizeof(Complex) << std::endl;
// 1. 单个对象
Complex* comPtr = new Complex;
delete comPtr;
return 0;
}
输出结果如下:
总结一下:
1. new expression 申请内存的步骤
1.1 调用 operator new 函数分配目标类型大小的内存空间,而 operator new 函数内存实际上调用的是 malloc 函数分配的内存
1.2 将申请到的内存块,由 void* 指针类型 强制转换为目标类型的指针
1.3 通过指针调用目标类的构造函数(只有编译器可以只有直接调用构造函数)
2. delete expression 释放内存的步骤
2.1 通过指针调用目标类的析构函数
2.2 调用 operator delete 释放对象内存,而 operator delete 内部实际是通过 free 函数释放分配的内存
三 operator new[] 与 operator delete[]
那么在执行 new[ ] 与 delete 两个 expression[ ] 时,内部发生了什么呢?(面试常问)
// new[ ] expression
Complex* ptr = new Complex[3];
// 等同于下面大括号内
{
Complex* ptr;
try {
void* mem = operator new[](size * sizeof(Complex)); // 1. 分配内存
ptr = (Complex*)mem; // 2. 指针类型转换
ptr->Complex::Complex(); // 3. 执行 3 次构造函数,从下标 i = 0, 1, 2 依次执行构造函数,只有编译器可以这样使用
}
cache(std::bad_alloc)
// 若是分配内存阶段出错,则不再执行 构造函数
}
// delete[ ] expression
delete ptr;
// 等同于下面大括号内
{
ptr->~Complex(); // 1. 执行 3 次析构函数,从下标 i = 2, 1, 0 依次执行析构函数
operator delete[](ptr); // 2. 释放内存
}
下面来验证一下内部发生的过程,先定义一下 class Complex
#include<iostream>
class Complex
{
public:
Complex():x(0),y(0)
{
std::cout << "Complex default consturctor. this = " << this << std::endl;
}
Complex(int x, int y):x(x),y(y)
{
std::cout << "Complex consturctor. this = "<< this <<" x: "<< x <<", y: "<< y << std::endl;
}
~Complex()
{
std::cout << "Complex desturctor. this = "<< this <<" x: "<< x <<", y: "<< y << std::endl;
}
// 用于给单个对象分配内存
void* operator new(std::size_t size)
{
std::cout << "Foo operator new size: " << size << std::endl;
return malloc(size);
}
void operator delete(void* ptr)
{
std::cout << "Foo operator delete " << std::endl;
return free(ptr);
}
// 用于给一组对象分配内存
void* operator new[](std::size_t size)
{
std::cout << "Foo operator new[] size: " << size << std::endl;
return malloc(size);
}
void operator delete[](void* ptr)
{
std::cout << "Foo operator delete[] " << std::endl;
return free(ptr);
}
// 标准库提供的 placement new 的重载形式
void* operator new(std::size_t size, void* ptr)
{
std::cout << "Foo operator operator new(std::size_t size, void* ptr), size: " << size << std::endl;
return ptr;
}
void operator delete(void* ptr1, void* ptr2)
{
std::cout << "Foo::operator delete(void*, void*)."<< std::endl;
}
private:
int x;
int y;
};
验证程序:
#include"complex.h"
int main()
{
// 输出 class 大小
std::cout << "sizeof(Complex): " << sizeof(Complex) << std::endl;
// 1. 单个对象
std::cout << "---new[] expression---" << std::endl;
Complex* comPtr = new Complex[3];
Complex* tmpPtr = comPtr;
// placement new
for(int i = 0; i < 3; i++)
{
new(tmpPtr++)Complex(i, i);
}
std::cout << "---delete[] expression---" << std::endl;
delete[] comPtr;
return 0;
}
输出:
从输出结果的构造函数的地址来看,地址是依次递增的,而执行析构函数时,地址正好是反回来的,说明是构造对象执行构造函数的顺序,与执行对象的析构函数的顺序是反过来的。
验证程序中在事先分配好的内存上,调用 placement new ,在已有的内存上构造对象。
总结:
1. new[] expression 申请内存的步骤
1.1 调用 operator new[] 函数分配目标类型大小的内存空间,而 operator new[] 函数内存实际上调用的是 malloc 函数分配的内存, 如:想要分配 size 个 Demo 类对象大小的内存,那么内存大小最终为 size * sizeof(Demo)。
1.2 将申请到的内存块,由 void* 指针类型 强制转换为目标类型的指针
1.3 通过指针依次调用 size 个目标类的构造函数(只有编译器可以只有直接调用构造函数)
调用顺序下标:从 0 , 1, ..., size - 1
2. delete[] expression 释放内存的步骤
2.1 通过指针依次调用 size 个目标类的析构函数,与构造函数的调用顺序正好相反,调用顺序下标:从 size - 1 , ..., 1 , 0
2.2 调用 operator delete 释放对象内存,而 operator delete 内部实际是通过 free 函数释放分配的内存
四 placement new
placement new 允许我们将对象构建与已分配好的内存上,没有所谓的 placment delete ,因为压根也没有专门为 placement 分配过内存。
char* buf = new char[sizeof (Complex)]; // 1. 分配内存
Complex* ptr = new(buf)Complex(0,0); // 2. 在已分配的内存上构造对象
delete [] buf; // 3. 释放内存
// 上面的 2 步在编译器中等同于下面
Complex* ptr;
try
{
void* ptr = operator new(sizeof(Complex), buf); // 1. 实际上就是将char* 指针转为 void*
ptr = static_cast<Complex*>(ptr); // 2. 将 void* 指针强转为 Complex*
ptr->Complex::Complex(); // 3. 利用 Complex* 指针调用构造函数
}
cache(std::bad_alloc)
{
// 若 allocate 分配失败,则 不再执行构造函数
}
验证程序如下:
#include"complex.h"
int main()
{
char* buf = new char[sizeof (Complex)];
Complex* ptr = new(buf)Complex(0,0);
delete [] buf;
return 0;
}
输出: