【C++学习笔记】new与malloc的区别
我们都知道new是用来在程序运行过程中为变量临时分配内存的C++关键字,那它跟C语言中的malloc都有些什么区别呢?
// c++11操作符
throwing(1) void* operator new (std::size_t size);
nothrow(2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
placement(3) void* operator new (std::size_t size, void* ptr) noexcept;
// C库函数头文件<stdlib.h>
void *malloc(size_t size);
1. 申请的内存所在位置
operator new
从自由存储区上为对象动态分配内存空间,而malloc
从堆上动态分配内存。
自由存储区是C++基于 operator new 的一个抽象概念,凡是通过 operator new 申请的内存,即为自由存储区。而堆是计算机操作系统的术语,是操作系统所维护的一块特殊内存,用于程序内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的相应内存。
那么new是否能在堆上动态分配内存呢?这取决于 operator new 的实现细节,自由存储区不仅可以是堆,还可以是静态存储区,这都看 operator new 在哪里为对象分配内存。new甚至可以不为对象分配内存!
new (place_address) type
place_address是一个指针,代表一块内存地址。当仅以一个地址调用new操作符时,new操作符会调用特殊的 operator new ,也就是上面的 palcement(3) 。
void* operator new (std::size_t size, void *ptr); // 此版本的operator new不允许重载
这个 operator new 不分配任何内存,只是简单地返回指针实参,然后由new表达式负责在 place_address 指定的地址进行对象的初始化工作。
2. 返回类型安全性
operator new 内存分配成功时,返回对象类型的指针,无须进行类型转换,因此是符合类型安全性的操作符。而malloc内存分配成功返回(void *)
,需要通过强制类型转换将(void*)
指针转换成我们需要的类型。
3. 内存分配失败时的返回值
new
内存分配失败时,会抛出bac_alloc exception
,它不会返回NULL
;malloc
分配内存失败时返回NULL
。
使用C语言时,我们习惯在malloc分配内存后判断分配是否成功
int *a = (int *)malloc(sizeof(int));
if (a == NULL) {
// ...
} else {
// ...
}
从C语言转到C++语言时可能会把这个习惯带入
int *a = new int();
if (a == NULL) {
// ...
} else {
// ...
}
实际上这样做没有一点意义,因为new根本不会返回NULL,程序能够执行到if
说明内存已经分配成功,否则早就抛异常了。正确的做法应该是使用异常机制
try {
int *a = new int();
} catch (bac_alloc) {
// ...
}
4. 是否需要指定内存大小
使用new操作符申请内存时无须指定内存块大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存大小。
malloc仅仅分配内存,free仅仅回收内存,而且是以字节为单位进行内存操作,并不执行构造和析构函数;new可以调用对象的构造函数,delete可以调用相应的析构函数,以具体类型为单位进行内存分配与释放。
class A {
public:
A() {}
~A() {}
}
A *ptr_new = new A;
A *ptr_malloc = (A *)malloc(sizeof(A)); // 当然这里使用malloc来为我们自定义类型分配内存其实并不怎么合适。
5. 是否调用构造/析构函数
使用operator new
操作符来分配对象内存时会经历三个步骤:
- 调用 operator new 函数(对于数组是 operator new[] )分配一块足够大的、原始的、未命名的内存空间。
- 编译器运行相应的构造函数以构造指定类型的对象并初始化。
- 对象构造完成后,返回一个指向该对象的指针。
使用 operator delete 操作符来释放对象内存时会经历两个步骤:
- 调用对象的析构函数。
- 编译器调用 operator delete (或 operator delete[] )函数释放内存空间。
总之来说new/delete
会调用对象的构造函数/析构函数以完成对象的构造/析构,而malloc
不会。
malloc
不具备内存初始化的特性,而new
在申请单个类型变量时可进行初始化 。
6. 对数组的处理
C++
提供了new[]/delete[]
专门处理数组类型
A *ptr_single new A;
A *ptr_arr = new A[10];
delete ptr_single;
delete[] ptr_arr;
new对数组的支持体现在它会对每一个数组元素分别调用构造函数进行初始化,释放对象时为每个对象调用析构函数。注意delete[]
要与new[]
配套使用,不然会出现数组对象部分释放的现象,造成内存泄漏。
而malloc
并不知道你在这块内存上要放的数组还是单个数据,反正它就给你一块原始内存,返回一个内存的地址。所以如果要动态分配一个数组的内存,需要我们手动指定数组大小
int * ptr = (int *)malloc(sizeof(int) * 10);
7. new与malloc是否可以相互调用
operator new/operator delete
的实现可以基于malloc
,但malloc
的实现不可以调用new
。 malloc/free
是C语言提供的标准库函数,使用时要包含头文件(C中#include <stdlib.h>
,C++中#include <cstdlib>
),在 C/C++ 中通用,可以覆盖; new/delete
是运算符,也是关键字,是C++的一部分,只能在C++中使用,并且可以重载。
void * operator new (sieze_t size)
{
if(void * mem = malloc(size)
return mem;
else
throw bad_alloc();
}
void operator delete(void *mem) noexcept
{
free(mem);
}
8.是否可以被重载
opeartor new/operator delete
可以被重载。标准库是定义了operator new
和operator delete
的12个重载版本
// new
throwing (1) void* operator new (std::size_t size);
nothrow (2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
placement (3) void* operator new (std::size_t size, void* ptr) noexcept;
throwing (1) void* operator new[] (std::size_t size);
nothrow (2) void* operator new[] (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
placement (3) void* operator new[] (std::size_t size, void* ptr) noexcept;
// delete
ordinary (1) void operator delete (void* ptr) noexcept;
nothrow (2) void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) noexcept;
placement (3) void operator delete (void* ptr, void* voidptr2) noexcept;
ordinary (1) void operator delete[] (void* ptr) noexcept;
nothrow (2) void operator delete[] (void* ptr, const std::nothrow_t& nothrow_constant) noexcept;
placement (3) void operator delete[] (void* ptr, void* voidptr2) noexcept;
我们可以自定义上面函数中的任意一个,前提是自定义版本必须位于全局作用域或者类作用域中。而malloc/free
并不允许重载。
9. 是否能够直观地重新分配内存
使用malloc
分配的内存后,如果在使用过程中发现内存不足,可以使用realloc
函数进行内存重新分配实现内存的扩充。realloc
先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;否则先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,然后释放原来的内存区域。
new
没有这样直观的配套设施来扩充内存。
10. 是否能够自定义如何处理内存分配不足
在operator new
抛出异常之前会先调用一个用户指定的错误处理函数,即new-handler
,这是一个指针类型
namespace std {
typedef void (*new_handler)();
}
指向一个没有参数没有返回值的函数——错误处理函数。为了指定错误处理函数,用户需要调用set_new_handler
这是一个声明于std
命名空间的标准库函数
namespace std {
new_handler set_new_handler(new_handler p) throw();
}
set_new_handler
的参数为new_handler
指针,指向operator new
无法分配足够内存时该调用的函数。其返回值也是个指针,指向set_new_handler
被调用前正在执行(但马上就要发生替换)的那个new_handler
函数。
对于malloc
用户并不能够去编程决定内存不足以分配时如何操作,只能看着malloc
返回NULL
。