| malloc() |
free() |
C函数 |
不可 |
| new |
delete |
C++表达式 |
不可 |
| ::operator new() |
::operator delete() |
C++函数 |
可 |
| allocator::allocate()
| allocator::deallocate()
| C++标准库 |
可自由设计并与之搭配任何容器 |
#include <iostream>
#include <complex>
using namespace std;
int main() {
void* p1 = malloc(521);
free(p1);
complex<int>* p2 = new complex<int>;
delete p2;
void* p3 = ::operator new(512);
::operator delete(p3);
#ifdef _MSC_VER
// 以下两个函数都是non-static,一定要通过object调用,以下分配3个int空间
int* p4 = allocator<int>().allocate(3, (int*)0);
// 上面的 (int*)0 暂时没有用
// 通过 allocator<int>() 创建要给临时对象,执行临死对象的allocate方法
allocator<int>().deallocate(p4, 3);
// 通过 allocator<int>() 创建要给临时对象,执行临死对象的deallocate方法
#endif // _MSC_VER
#ifdef __BORLANDC__
// 以下两个函数都是non-static,一定要通过object调用,以下分配5个int空间
int* p4 = allocator<int>().allocator(5);
allocator<int>().deallocate(p4, 5);
#endif // __BORLANDC__
#ifdef __GNUC__
// 以下两个函数都是static,可通过全名调用,一下分配512bytes
void* p4 = alloc::allocate(512);
alloc::deallocate(p4, 512);
#endif // __GNUC__
return 0;
}
上述代码为四种内存分配的基本用法
class Complex {
public:
Complex(int x, int y) {
m_x = x;
m_y = y;
std::cout << "constructor Complex" << std::endl;
}
Complex() {
m_x = 0;
m_y = 0;
std::cout << "constructor Complex" << std::endl;
}
private:
int m_x;
int m_y;
};
void func_new() {
Complex* p1 = new Complex(1, 2);
delete p1;
// 等价于
Complex* p2;
try
{
void* mem = operator new(sizeof(Complex));
p2 = static_cast<Complex*>(mem);
p2->Complex::Complex(1, 2);
operator delete(p2);
// 这里直接调用构造函数的动作 只有编译器才可以这么写 自己写不同平台可能报错
}
catch (std::bad_alloc)
{
}
}
tip: 上述代码中说明了不可直接pc->Complex::Complex(1,2)方法,如果想直接调用构造函数可以运用placement new,写法是new(p)Complex(1,2)
// gcc 中 operator new的实现方式
void *operator new(std::size_t size) _THROW_BAD_ALLOC
{
if (size == 0)
size = 1;
void* p;
while ((p = ::malloc(size)) == nullptr)
{
// If malloc fails and there is a new_handler,
// call it to try free up memory.
std::new_handler nh = std::get_new_handler();
if (nh)
nh();
else
#ifndef _LIBCPP_NO_EXCEPTIONS
throw std::bad_alloc();
#else
break;
#endif
}
return p;
}
从上面的代码可以看到,new的实现方式就是调用了operator new方法,在强转之后执行了对应的构造函数
而operator new方法中,真正分配内存的是malloc方法
网上看到的资料
Complex* pc = new Complex(1, 2);
// ... some operation
delete pc;
// 编译器将delete转换成下面操作
pc->~Complex(); // 先析构
operator delete(pc); // 然后释放内存
上述为delete的操作,这里直接调用析构函数不会报错
void operator delete(void* ptr) noexcept
{
::free(ptr);
}
operator delete底层通过free释放内存
上述代码中pc->Complex::Complex(1, 2)直接执行构造函数没有报错,并不能表示我们可以都这么直接调用构造,下面代码就是一个反例
string* pstr = new string;
pstr->string::string();
pstr->~string();
delete pstr;
这里的代码会明显报错class std::basic_string<char> has no member named string
这里报错的原因是string本来的名字其实叫basic_string,是被typedef成string,所以string的构造函数应该是basic_string()才对
除了typedef导致构造函数名字不对的问题之外,编译器的严格程度不同也会出现报错,即存在平台差异性,所以不推荐手动调用构造函数
array new / array delete
Complex* pca = new Complex[3]; // 唤起三次构造函数
// ... some operation
delete[] pca; // 唤起三次析构
如果写的是delete而不是delete[],那么编译器只会执行一次析构函数,而不是三次,也就是说有两个对象的析构函数没有执行,如果对象中存在new操作,这样会导致无法执行delete进而导致内存泄漏

从上图中可以看到,new出的内存块除了包含三个Complex对象外,还包含一些cookie信息,这些信息帮助操作系统释放整块内存,而delete与delete[]区别仅仅在于数组中所有对象是否都执行析构
class A{
public:
int id;
A(): id(0) { std::cout << "default " << this << " " << this->id << std::endl; };
A(int i): id(i) { std::cout << this << " " << "int cons" << i << std::endl; }
~A() { std::cout << " ~ " << this << " " << this->id << std::endl; }
};
void test_placement_new() {
A* buf = new A[3]; // 调用A的默认构造函数,创建对象
A* tmp = buf;
for (int i = 0; i < 3; i++) {
new(tmp++)A(i); // placement new的写法,在tmp地址的构建A对象,调用A的构造函数
}
delete[] buf;
}
default 00A64CF4 0
default 00A64CF8 0
default 00A64CFC 0
00A64CF4 int cons0
00A64CF8 int cons1
00A64CFC int cons2
~ 00A64CFC 2
~ 00A64CF8 1
~ 00A64CF4 0
通过查看输出,可以发现这上述代码的执行顺序
placement new
placement new 允许我们将object建构于allocated memory(已经分配的内存)中
- 没有
placement delete,因为placement new根本没有分配内存
因为是构建于已经分配的内存中,所以需要一个现成的被分配的内存空间的指针
#include <new>
void func(){
char* buf = new char[sizeof(Complex) * 3];
Complex* pc = new(buf)Complex(1, 2);
// ....
delete[] buf;
}
void func_1(){
Complex* pc = new(buf)Complex(1, 2);
// 上面一行代码 等价下面的代码
Complex* pc;
try{
void* mem = operator new(sizeof(Complex), buf);
pc = static_cast<Complex*>(mem);
pc->Complex::Complex(1, 2);
}
catch(std::bad_alloc){
}
}
// 下面为当 operator new的两个参数为size_t和void*的源码
void* operator new(size_t, void* loc){
return loc;
}
上面的代码使用了 placement new,它可以让你在已经分配好的内存上构造对象。placement new的语法是:new (address) (type) initializer。其中,address 是你指定的内存地址,type 是你要构造的对象的类型,initializer 是你要传递给对象构造函数的参数
定位 new 操作符的优点是可以提高性能和异常安全性,因为它不需要再次分配内存,而且可以在程序员控制的内存上创建对象。定位 new 操作符的缺点是需要手动调用对象的析构函数来释放资源,而且不能使用 delete 操作符来删除对象,只能删除分配给它们的内存。
分析上述代码可以发现
operator new(size_t, void* loc)什么都没做,因为此时loc是已经分配好了的内存地址,所以不需要再分配内存
placement new其实就是执行了一次构造函数
C++分配内存的途径

一般来说,我们会重载类的Foo::operator new和Foo::operator delete,将对象的创建和内存分配自己来控制(通过自己控制可以减少内存块上的cookie的使用)
Foo*p = (Foo*)malloc(sizeof(Foo));
new(p)Foo(x);
// ...
p->~Foo();
free(p);
当然也可以重载全局::operator new / ::operator delete
// 测试重写代码
void* myAlloc(size_t size){
return malloc(size);
}
void myFree(void* ptr){
return free(ptr);
}
inline void* operator new(size_t size){
cout << "my new" << std::endl;
return myAlloc(size);
}
inline void* operator new[](size_t size){
cout << "my new[]" << std::endl;
return myAlloc(size);
}
inline void operator delete(void* ptr){
cout << "my delete" << std::endl;
myFree(ptr);
}
inline void operator delete[](void* ptr){
cout << "my delete[]" << std::endl;
myFree(ptr);
}
更常见的写法是类中重载
class Foo {
public:
int _id;
long _data;
std::string _str;
public:
Foo() : _id(0) { std::cout << "default ctor " << this << " " << this->_id << std::endl; }
Foo(int i): _id(i) { std::cout << "ctor " << this << " " << this->_id << std::endl; }
virtual ~Foo() { std::cout << "dtor " << this << std::endl; }
static void* operator new(size_t size);
static void operator delete(void* pdead, size_t size);
static void* operator new[](size_t size);
static void operator delete[](void* pdead, size_t size);
};
void* Foo::operator new(size_t size) {
Foo* p = (Foo*)malloc(size);
std::cout << "my alloc Foo " << p << std::endl;
return p;
}
void Foo::operator delete(void* pdead, size_t size) {
std::cout << "my delete " << pdead << std::endl;
free(pdead);
}
void* Foo::operator new[](size_t size) {
Foo* p = (Foo*)malloc(size);
std::cout << "my alloc Foo " << p << std::endl;
return p;
}
void Foo::operator delete[](void* pdead, size_t size) {
std::cout << "my delete " << pdead << std::endl;
free(pdead);
}
int main() {
std::cout << sizeof(Foo) << std::endl;
Foo* p = new Foo(7);
delete p;
Foo* pArray = new Foo[5];
delete[] pArray;
return 0;
}
40
my alloc Foo 00C2DA78
ctor 00C2DA78 7
dtor 00C2DA78
my delete 00C2DA78
my alloc Foo 00C30110
default ctor 00C30114 0
default ctor 00C3013C 0
default ctor 00C30164 0
default ctor 00C3018C 0
default ctor 00C301B4 0
dtor 00C301B4
dtor 00C3018C
dtor 00C30164
dtor 00C3013C
dtor 00C30114
my delete 00C30110
但是,如果使用::new和::delete则会绕过类定义的operator new / operator delete方法,转而执行全局的new / delete
int main() {
std::cout << sizeof(Foo) << std::endl;
Foo* p = ::new Foo(7);
::delete p;
Foo* pArray = ::new Foo[5];
::delete[] pArray;
return 0;
}