在 C/C++ 中,new/deletemalloc/free 的使用涉及内存分配和释放的机制,虽然它们的功能表面上类似,但在使用上存在许多本质上的区别,使用时务必配对使用,不可混用。


一、new/deletemalloc/free 的对比分析

1.1 内存分配的来源

new/delete:内存从“自由存储区”(Free Store)分配。自由存储区是专门为 C++ 中对象管理的内存区域,它的管理由 C++ 的内存分配机制决定。new 分配的内存适合构造对象,因为它会自动调用构造函数。

malloc/free:内存从“堆”(Heap)分配。这是 C 语言标准中的内存分配机制,malloc 只知道要分配的字节数,不会涉及到对象的构造和析构。

说到这里,不得不简单谈一下内存分区。事实上,内存分区没有一个统一的定义,根据 CSAPP 这本书,我们可以将内存分为:代码区、全局/静态存储区、堆区、栈区以及常量区。我们可以将前述的 ”自由存储区“ 视为堆区,但其不仅可以是堆区,还可以是静态存储区,这由 operator new 的具体实现决定。

1.2 返回类型

new/deletenew 返回一个完全类型化的指针,即指向特定类型的对象,因此不需要手动进行类型转换。如果分配失败,new 会抛出异常(std::bad_alloc),这与返回 NULL 的方式不同。当然,我们也可使用关键字 std::nothrow 使其在分配失败时返回 nullptr

malloc/freemalloc 返回 void* 指针,意味着需要手动将其转换为所需的类型。并且当 malloc 分配失败时,它会返回 NULL,这要求使用者在每次分配时检查返回值。

1.3 内存大小的计算

new/delete:编译器在编译时根据类型自动计算所需的内存大小,因此不需要手动提供内存的大小。尤其在处理类对象时,编译器会根据对象的类型自动分配所需的空间。

malloc/free:调用 malloc 时,必须明确指定要分配的字节数,这意味着开发人员需要自己计算复杂类型的大小。这在处理数组或结构体时显得尤其繁琐。

1.4 数组分配

new/delete:C++ 提供了 new[]delete[] 来专门分配和释放数组,这些操作会调用每个对象的构造函数和析构函数,确保数组中每个元素被正确初始化和销毁。

malloc/freemalloc 分配数组时,需要开发者自己手动计算总的字节大小,而 free 不会调用析构函数,因此需要开发者手动管理数组元素的初始化和销毁。

1.5 对象的构造与析构

new/deletenew 会自动调用对象的构造函数来初始化分配的内存,delete 则会调用析构函数来清理对象的资源。这一点对需要进行资源管理(如动态内存、文件句柄等)的类尤为重要。

malloc/freemalloc 仅仅分配内存,不会调用构造函数,因此对象不会自动初始化。同样,free 也不会调用析构函数,仅仅是释放分配的内存。

1.6 内存重新分配

new/deletenew 没有提供重新分配内存的机制。对于重新分配,需要手动管理复制和删除旧的对象。这在处理复杂类型时会涉及拷贝构造函数和析构函数的调用。

malloc/freemalloc 可以与 realloc 一起使用,实现更大的内存块分配,而无需担心对象的构造和析构。对于简单的内存块操作,realloc 更为直观和方便。

1.7 内存不足处理

new/delete:C++ 提供了 std::set_new_handler,可以自定义内存不足时的处理方式,这允许开发者在内存不足时采取一些措施(如释放内存或记录日志)。

malloc/freemalloc 没有类似的机制来处理低内存情况。当系统内存不足时,malloc 直接返回 NULL,用户无法在这一点插入自己的代码来进行处理。

1.8 是否可以重载

new/delete:C++ 允许用户重载 operator newoperator delete,这样可以自定义内存分配和释放的行为。例如,可以实现自定义的内存池或内存跟踪机制。

malloc/freemallocfree 是标准库函数,不能被合法重载,因此不支持自定义内存管理行为。