一、问题
有了 malloc/free 为什么还要 new/delete?
二、解析
malloc 与 free 是 C++/C 语言的标准库函数, new/delete 是 C++的运算符。它们都可用于申请动态内存和释放内存。
对于非内部数据类型的对象而言,光用 maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于 malloc/free。
因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符 new,以及一个能完成清理与释放内存工作的运算符 delete。注意 new/delete 不是库函数。
三、new的底层实现
阅读STL源码,空间配置器(allocate)的实现
template <class T>
inline T* allocate(ptrdiff_t size, T*) {
set_new_handler(0); // 设置内存申请失败的回调函数
T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
if (tmp == 0) { // 申请内存失败
cerr < "out of memory" << endl;
exit(1):
}
return tmp;
}
template <class T>
inline void deallocate(T*buffer) {
::operator delete(buffer);
}
或许你没有听过 operator new(),只听过 new()。其实 new() 底层是调用 operator new() 来实现的。而operator new() 其实还是调用 malloc()。下面展示的是 VC 下的 operator new() 的源码,可以很清楚的看到它其实也是调用 malloc() 进行内存分配的。
void *operator new(size_t size, const std::nothrow_t&)
_THROW0()
{ // try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
{
// buy more memory or return null pointer
_TRY_BEGIN
if (_callnewh(size) == 0) break;
_CATCH(std:bad_alloc) return (0);
_CATCH_END
}
return (p);
}
分析:调用malloc失败后会调用_callnewh。如果_callnewh返回0则抛出bac_alloc异常,返回非零则继续分配内存。这个_callnewh是什么呢?它是一个new handler,通俗来讲就是new失败的时候调用的回调函数,可以通过_set_new_handler来设置。
下面是一个简单的示例程序,new操作分配内存失败时将调用no_memory函数:
Typedef void (*new_handler)(); // 定义函数指针
new_handler set_new_handler(new_handler new_p) throw();//C++98
new_handler set_new_handler (new_handler new_p) noexcept;//C++11
// new_handler example
#include <iostream> // std::cout
#include <cstdlib> // std::exit
#include <new> // std::set_new_handler
// 自定义处理函数
void no_memory () {
std::cout << "Failed to allocate memory!\n";
std::exit (1);
}
int main () {
std::set_new_handler(no_memory); // new失败后,进行相应的处理
std::cout << "Attempting to allocate 1 GiB...";
char* p = new char [1024*1024*1024];
std::cout << "Ok\n";
delete[] p;
return 0;
}
既然allocator 最终的都是调用 malloc() 来分配大小,为什么又弄个operator new? alloc 最主要的作用就是要尽量减少 malloc() 的次数,而 malloc() 的次数越多所带来的额外开销就越大,会形成更多的内存碎片。
四、new和operator new之间的关系
其实, 当我们使用关键字new在堆上动态创建一个对象时,它实际上做了三件事:
1、分配一块内存空间
2、调用构造函数生成类对象
3、返回正确的指针。
当然,如果我们创建的是简单类型的变量,那么第二步会被省略。
事实上,分配内存这一操作就是由operator new(size_t)来完成的,如果类A重载了operator new,那么将调用A::operator new(size_t ),如果没有重载,就调用::operator new(size_t ),全局new操作符由C++默认提供。因此前面的两步也就是:
1.调用operator new
2.调用构造函数。
举个例子:
class A
{
private:int m_temp;
public:
A(int temp) :m_temp(temp){}
void Say() { printf("i=%d/n", i); }
};
A* pa = new A(3);//调用new:
那么上述动态创建一个对象的过程大致相当于以下三句话:
A* pa = (A*)malloc(sizeof(A));
pa->A::A(3);
return pa;
虽然从效果上看,这三句话也得到了一个有效的指向堆上的A对象的指针pa,但区别在于,当malloc失败时,它不会调用分配内存失败处理程序new_handler,而使用new的话会的。因此我们还是要尽可能的使用new,除非有一些特殊的需求。
同理,delete操作也是两个步骤
1、调用析构函数~A();
2、::operator delete 将内存释放
五、重载operator new
因为new
是关键字,我们本应该无法修改new
分配内存的方式。由于new
在分配内存时,调用operator new
。所以重载operator new
就可以修改分配内存的方式了。
class Foo {
public:
void* operator new(std::size_t size)
{
std::cout << "operator new" << std::endl;
return std::malloc(size);
}
};
int main()
{
Foo* m = new Foo;
std::cout << sizeof(m) << std::endl;
delete m;
return 0;
}
operator new
返回值必须是void*
。第一个参数必须是size_t
,还可加其它参数,下文讨论。
operator new
重载可以放在全局中,也可以放到类内部。当编译器发现有new
关键字,就会现在类和其基类中寻找operator new
,找不到就在全局中找,再找不到就用默认的。
在类中的operator new
默认就是static
。所以加static
可以,不加也是全局,可以正常使用。
六、operator new
加入其它形参
上文说到operator new
第一个参数必须是size_t
并且可以重载加入其它形参。
这就引出placement new
。原型是这样:
class Foo {
public:
void* operator new(std::size_t size)
{
std::cout << "operator new" << std::endl;
return std::malloc(size);
}
void* operator new(std::size_t size, void* ptr)
{
std::cout << "placement new" << std::endl;
return ptr;
}
};
int main()
{
Foo* m = new Foo;
Foo* m2 = new(m) Foo;
std::cout << sizeof(m) << std::endl;
// delete m2;
delete m;
return 0;
}
placement new
就是operator new
重载的一种形式。上文说到,operator new
的主要作用就是分配空间,初始化对象的工作是new
关键字的。
经过这样重载,placement new
完全没有分配新的空间,而是把ptr
的地址传了出去。由于调用构造函数的工作是new
关键字,所以placement new
不影响对象初始化。
placement new
由于不需要申请新内存和释放旧内存。所以在内存池中,意外的便捷。
七、重载operator delete
void operator delete(void* ptr)
{
std::cout << "operator delete" << std::endl;
std::free(ptr);
}
delete
与new
类似,只不过返回值必须为void
。
参考:
C++ 内存分配(new,operator new)详解 - 王小北 - 博客园
C++ 内存管理之重载operator new 和operator delete_FBI-PC的博客-CSDN博客_operator delete