在 C/C++ 中,new/delete
与 malloc/free
的使用涉及内存分配和释放的机制,虽然它们的功能表面上类似,但在使用上存在许多本质上的区别,使用时务必配对使用,不可混用。
一、new/delete
与 malloc/free
的对比分析
1.1 内存分配的来源
new/delete
:内存从“自由存储区”(Free Store)分配。自由存储区是专门为 C++ 中对象管理的内存区域,它的管理由 C++ 的内存分配机制决定。new
分配的内存适合构造对象,因为它会自动调用构造函数。
malloc/free
:内存从“堆”(Heap)分配。这是 C 语言标准中的内存分配机制,malloc
只知道要分配的字节数,不会涉及到对象的构造和析构。
说到这里,不得不简单谈一下内存分区。事实上,内存分区没有一个统一的定义,根据 CSAPP 这本书,我们可以将内存分为:代码区、全局/静态存储区、堆区、栈区以及常量区。我们可以将前述的 ”自由存储区“ 视为堆区,但其不仅可以是堆区,还可以是静态存储区,这由 operator new
的具体实现决定。
1.2 返回类型
new/delete
:new
返回一个完全类型化的指针,即指向特定类型的对象,因此不需要手动进行类型转换。如果分配失败,new
会抛出异常(std::bad_alloc
),这与返回 NULL
的方式不同。当然,我们也可使用关键字 std::nothrow
使其在分配失败时返回 nullptr
。
malloc/free
:malloc
返回 void*
指针,意味着需要手动将其转换为所需的类型。并且当 malloc
分配失败时,它会返回 NULL
,这要求使用者在每次分配时检查返回值。
1.3 内存大小的计算
new/delete
:编译器在编译时根据类型自动计算所需的内存大小,因此不需要手动提供内存的大小。尤其在处理类对象时,编译器会根据对象的类型自动分配所需的空间。
malloc/free
:调用 malloc
时,必须明确指定要分配的字节数,这意味着开发人员需要自己计算复杂类型的大小。这在处理数组或结构体时显得尤其繁琐。
1.4 数组分配
new/delete
:C++ 提供了 new[]
和 delete[]
来专门分配和释放数组,这些操作会调用每个对象的构造函数和析构函数,确保数组中每个元素被正确初始化和销毁。
malloc/free
:malloc
分配数组时,需要开发者自己手动计算总的字节大小,而 free
不会调用析构函数,因此需要开发者手动管理数组元素的初始化和销毁。
1.5 对象的构造与析构
new/delete
:new
会自动调用对象的构造函数来初始化分配的内存,delete
则会调用析构函数来清理对象的资源。这一点对需要进行资源管理(如动态内存、文件句柄等)的类尤为重要。
malloc/free
:malloc
仅仅分配内存,不会调用构造函数,因此对象不会自动初始化。同样,free
也不会调用析构函数,仅仅是释放分配的内存。
1.6 内存重新分配
new/delete
:new
没有提供重新分配内存的机制。对于重新分配,需要手动管理复制和删除旧的对象。这在处理复杂类型时会涉及拷贝构造函数和析构函数的调用。
malloc/free
:malloc
可以与 realloc
一起使用,实现更大的内存块分配,而无需担心对象的构造和析构。对于简单的内存块操作,realloc
更为直观和方便。
1.7 内存不足处理
new/delete
:C++ 提供了 std::set_new_handler
,可以自定义内存不足时的处理方式,这允许开发者在内存不足时采取一些措施(如释放内存或记录日志)。
malloc/free
:malloc
没有类似的机制来处理低内存情况。当系统内存不足时,malloc
直接返回 NULL
,用户无法在这一点插入自己的代码来进行处理。
1.8 是否可以重载
new/delete
:C++ 允许用户重载 operator new
和 operator delete
,这样可以自定义内存分配和释放的行为。例如,可以实现自定义的内存池或内存跟踪机制。
malloc/free
:malloc
和 free
是标准库函数,不能被合法重载,因此不支持自定义内存管理行为。