new与malloc区别
前言:动态分配内存,我们经常用到的是new与malloc,很多童鞋没有搞清楚这俩的本质区别,导致了代码产生了一些非预期现象。今天跟大家一起来扒一扒它们的区别。
目录
一、new与malloc基本概述
二、主要区别解析
2.1 自由存储区 vs 堆
2.2 指定类型指针 vs void* 指针
2.3 构造函数与析构函数
2.4 异常处理
三、代码示例
一、new与malloc基本概述
new和malloc都是用于在运行时动态分配内存的机制,但它们有着本质的区别。
new是 C++ 语言中的关键字(操作符),专门用于在自由存储区(free store)上分配内存并构造对象。它不仅负责分配内存,还会调用对象的构造函数来完成对象的初始化,从而确保对象在使用前处于正确状态。
delete是与之配套的释放操作,会调用对象的析构函数并释放内存。
这一整套机制是 C++ 提供的内存管理方案,旨在通过自动调用构造/析构函数来简化对象的生命周期管理。
相对而言,malloc是 C 语言标准库中的库函数(包含在<cstdlib>或<stdlib.h>中),用于在堆(heap)上动态分配指定字节数的原始内存块。它仅仅分配内存,不进行任何初始化操作。
与之配套的free函数用于释放先前通过 malloc 分配的内存。malloc/free 是 C 语言进行动态内存管理的主要手段,需要手动计算所需内存的大小,并在不再需要时显式调用 free 来释放内存,否则将导致内存泄漏。
二、主要区别解析
2.1 自由存储区 vs 堆
new从自由存储区(free store)上为对象分配内存。自由存储区是 C++ 为了支持 new 操作而引入的一个抽象概念,凡是使用 new 分配的内存都属于自由存储区。自由存储区在实现上可以基于堆,但不一定等同于堆。例如,可以通过重载operator new使得 new 从静态存储区甚至非堆内存区域分配对象。
C++ 标准并未严格限定自由存储区的具体位置,new 分配的内存位置取决于底层实现,典型情况下是由 C++ 运行时在堆上分配。
malloc直接从堆上分配内存。堆是操作系统维护的一块用于动态内存分配的内存区域,属于计算机系统的底层概念。当调用 malloc 时,它请求操作系统在堆中开辟一块指定大小的内存,并返回该内存的起始地址。由于 malloc 直接使用堆,它没有类似 C++的内存管理机制。
在实际应用中,new 和 malloc 通常都会在进程的堆内存区域分配空间,但从概念上讲,new 分配的是自由存储区,malloc 分配的是堆。
2.2 指定类型指针 vs void* 指针
new分配内存时,编译器会根据请求的类型信息自动计算所需大小,并返回指向该类型的指针,无需进行类型转换。
例如,int* p = new int;会分配一个 int 所需的内存并返回 int* 类型的指针。这种设计保证了类型安全——编译器会确保 new 返回的指针类型与请求的类型匹配,保证了类型安全。
malloc分配内存时,需要显式指定所需内存的字节数,并且 malloc 返回的是void*类型的通用指针。void* 可以指向任何类型的数据,但本身不包含类型信息。因此,使用 malloc 分配内存时,通常需要将返回的 void* 指针强制转换为所需的指针类型。
例如,int* p = (int*)malloc(sizeof(int));。这里,sizeof(int)计算出 int 类型需要的字节数,malloc 返回的 void* 被显式转换为 int*。这种类型转换增加了出错的机会(如果类型不匹配,可能引发未定义行为)。此外,由于 malloc 无法在编译时进行类型检查,相比 new 缺乏类型安全性。
2.3 构造函数与析构函数
这是 new 与 malloc最核心的区别之一。new在分配内存的同时,会自动调用对象的构造函数来初始化对象。当执行T* p = new T(args);时,编译器会完成两件事:
- 调用
operator new(底层通常基于 malloc)分配足够大小的原始内存。 - 在分配的内存上运行构造函数,将对象初始化为指定状态。
例如,new A()会先调用operator new(sizeof(A))分配内存,然后调用A::A()构造函数,通过 new 分配的对象在使用前就已经完成了必要的初始化。
delete在释放内存时,会先自动调用对象的析构函数来清理对象占用的资源,然后再释放内存。执行delete p;时,会先调用p->~T()析构函数,然后调用operator delete(底层通常基于 free)回收内存。
new/delete 通过自动调用构造/析构函数,实现了对对象生命周期的完整管理。
与之相对,malloc不调用任何构造函数或析构函数。malloc 分配的是原始的、未初始化的内存块,其内容是未定义的。必须手动调用对象的构造函数来初始化,否则对象将处于未初始化状态。
同样地,通过free释放内存时,也不会调用任何析构函数,必须手动调用这些析构逻辑,否则会造成资源泄漏。简而言之,malloc/free 只管内存的分配和释放,不涉及对象的构造和析构,增加了程序员的管理负担。
2.4 异常处理
当内存分配失败(例如系统内存耗尽)时,new和malloc的处理方式也不同。
new默认会抛出异常。C++ 标准规定,如果 new 无法分配足够的内存,它会抛出一个std::bad_alloc类型的异常。分配失败,控制流会被异常机制立即中断。程序员可以通过 try-catch 块来捕获std::bad_alloc异常,从而处理内存分配失败的情况。例如:
try { int* p = new int[10000000]; // 尝试分配超大数组 } catch (const bad_alloc& e) { cerr << "内存分配失败: " << e.what() << endl; // 进行错误处理,例如释放资源、退出程序等 }malloc在分配失败时返回 NULL 指针。由于 malloc 是 C 语言风格的函数,它没有异常机制,因此通过返回值来指示错误。在使用 malloc 后必须检查返回值是否为 NULL,以判断分配是否成功。如果忽略检查,直接使用空指针,将导致未定义行为(程序崩溃)。例如
int* p = (int*)malloc(sizeof(int) * 10000000); if (p == NULL) { // 内存分配失败的处理 fprintf(stderr, "内存分配失败\n"); exit(EXIT_FAILURE); } // 否则,安全地使用 p...三、代码示例
构造函数与析构函数的调用与否,是new与malloc的最核心区别。这里我们通过代码示例展示一下
#include <iostream> using namespace std; class A { public: A() : val(66) { cout << "A的构造函数被调用" << endl; } ~A() { cout << "A的析构函数被调用" << endl; } int val; }; int main() { // 使用 new 分配并初始化对象 cout << "--- 使用 new ---" << endl; A* a = new A(); // 会调用 A 的构造函数 cout << "a->val = " << a->val << endl; delete a; // 会调用 A 的析构函数 // 使用 malloc 分配内存但不调用构造函数 cout << endl << "--- 使用 malloc ---" << endl; A* aa = (A*)malloc(sizeof(A)); // 只分配内存,不调用构造函数 // 注意:此时 aa->val 的值是未定义的(垃圾值) // 手动调用构造函数来初始化对象 new(aa)A(); // 使用 placement new 在 aa 所指内存上调用构造函数 cout << "aa->val = " << aa->val << endl; // 手动调用析构函数清理对象,然后释放内存 aa->~A(); free(aa); return 0; }可以看到,使用 new 时构造函数和析构函数被自动调用,而使用 malloc 时需要手动通过 placement new 调用构造函数,并在 free 前手动调用析构函数。如果省略这些手动步骤,malloc 分配的对象将不会被正确初始化或清理。
写在最后:在现代 C++ 中,推荐优先使用智能指针(如
std::unique_ptr、std::shared_ptr)来管理动态分配的对象,而不是直接使用 new/delete。对于智能指针的解读,感兴趣的童鞋可以看这篇文章:C++智能指针 解读_cpp智能指针-CSDN博客
