0

0

C++堆内存分配 new和malloc对比

P粉602998670

P粉602998670

发布时间:2025-08-25 09:11:01

|

786人浏览过

|

来源于php中文网

原创

new是C++中用于动态分配内存并自动调用构造函数的操作符,而malloc是C语言中仅分配原始内存的库函数,不调用构造函数;new具有类型安全、异常处理和与C++对象模型融合的优势,malloc适用于与C库交互、底层内存管理等特定场景;在C++中推荐使用new结合智能指针和RAII原则来安全管理内存,避免资源泄漏。

c++堆内存分配 new和malloc对比

new
是C++语言层面的运算符,它在分配内存的同时,还会自动调用对象的构造函数,确保对象被正确初始化。而
malloc
则是C标准库中的一个函数,它仅仅负责从堆上划拨出一块原始的、未初始化的内存空间,不涉及任何对象的构造行为。这两者在C++的内存管理中扮演着不同的角色,并且在使用习惯、类型安全性、错误处理机制以及与C++特性融合度上都有着本质的区别

解决方案

说实话,在C++里谈堆内存分配,

new
malloc
这哥俩总是绕不开的话题。它们的核心差异,简单讲就是:
new
是“面向对象”的,
malloc
是“面向字节”的。

首先,从本质上看,

new
是一个操作符(或者说关键字),它能被重载,并且是C++语言规范的一部分。它不仅分配内存,更重要的是,它会调用你所创建对象的构造函数。当你写
MyClass* obj = new MyClass();
,背后发生的是:先分配足够
MyClass
对象大小的内存,然后在这块内存上调用
MyClass
的默认构造函数。而
malloc
呢,它只是一个库函数,原型是
void* malloc(size_t size)
。它只管给你一块指定大小的原始内存,这块内存里有什么?天知道,它可不管。所以,你如果用
malloc
给C++对象分配内存,你还得自己手动调用“placement new”来构造对象,用完还得手动调用析构函数,最后再
free
掉内存。这听起来就麻烦,而且容易出错。

其次,类型安全性上,

new
明显更胜一筹。
new
返回的是一个指向特定类型的指针(比如
MyClass*
),你不需要进行强制类型转换。编译器知道你正在创建一个
MyClass
类型的对象,它能帮你检查类型匹配性。但
malloc
返回的是
void*
,这意味着你需要手动将其转换为你需要的类型(比如
MyClass* obj = (MyClass*)malloc(sizeof(MyClass))
)。这种强制转换不仅增加了代码的冗余,更重要的是,它绕过了C++的类型检查,增加了潜在的类型不匹配错误风险。

立即学习C++免费学习笔记(深入)”;

再来聊聊错误处理

new
在默认情况下,如果内存分配失败,它会抛出
std::bad_alloc
异常。这是C++处理错误的一种标准且优雅的方式,你可以通过
try-catch
块来捕获并处理。这种方式与C++的异常处理机制完美融合。而
malloc
呢,如果分配失败,它会返回一个
NULL
指针。这意味着你每次调用
malloc
后都必须显式地检查返回值是否为
NULL
,否则直接解引用
NULL
指针就会导致程序崩溃。虽然
new
也有
new(std::nothrow)
的形式,它在分配失败时返回
NULL
而不是抛异常,但在绝大多数C++代码中,使用异常处理是更推荐的做法。

最后,数组分配也是个考量点。

new
有专门的
new[]
语法来分配对象数组,它会正确地为数组中的每个元素调用构造函数,并在
delete[]
时为每个元素调用析构函数。
malloc
虽然也能分配一块足够大的内存来存放数组,但它不会管数组元素的构造和析构,你得自己循环处理。这再次印证了
new
在处理C++对象时的便利性和安全性。

总结一下,

new
是C++为对象而生的内存分配方式,它封装了内存分配和对象构造/析构的复杂性,提供了类型安全和异常处理机制。
malloc
则更像一个底层的工具,它只管分配原始内存,不关心内存里住着什么“东西”。

为什么在C++中推荐使用
new
而非
malloc
进行内存分配?

这其实是个老生常谈的问题了,但每次讨论都觉得有必要再强调一遍。简单来说,

new
和C++的“面向对象”设计哲学是高度契合的。当我们用C++编程时,我们通常在操作对象,而不仅仅是原始的字节块。

首先,最直接的理由就是对象的生命周期管理

new
操作符不只是分配内存那么简单,它还承担了调用对象构造函数的职责。这意味着,当你
new
一个对象时,这个对象会被正确地初始化,它的成员变量会得到合理的值,它的资源(比如文件句柄、网络连接等)也会被妥善地获取。对应地,当你用
delete
来释放内存时,对象的析构函数会被自动调用,确保所有资源被正确释放,避免内存泄漏或资源泄漏。而
malloc
free
就完全是“粗放式管理”了,它们只管内存的分配和回收,至于内存里住着的“对象”过得好不好,有没有初始化,有没有正确清理,它们一概不管。这就要求开发者必须手动处理构造和析构,这无疑增加了代码的复杂性和出错的概率。想想看,如果一个类有复杂的构造逻辑或者需要管理外部资源,用
malloc
后你还得手动调用
placement new
和析构函数,这简直是自找麻烦,也违背了C++提倡的RAII(资源获取即初始化)原则。

其次,类型安全性

new
的另一大优势。
new
返回的是一个指向特定类型的指针,比如
MyClass*
。编译器在编译时就能检查类型匹配,如果类型不兼容,会直接报错。这能在早期发现很多潜在的类型错误。
malloc
返回的却是
void*
,你需要强制类型转换。这种C风格的强制转换,在C++中其实是应该尽量避免的,因为它相当于告诉编译器“我知道我在做什么,别管我”,从而绕过了编译器的类型检查。一旦类型转换错误,运行时就可能出现难以追踪的bug,比如内存访问越界或者错误的类型解释。

再者,

new
与C++的异常处理机制无缝衔接。当内存分配失败时,
new
默认会抛出
std::bad_alloc
异常。这种基于异常的错误处理方式,是C++处理运行时错误的标准范式,它使得错误处理逻辑与正常业务逻辑分离,代码更清晰。而
malloc
则返回
NULL
,这就要求你每次调用后都得显式地检查
NULL
,大量的
if (ptr == NULL)
检查会使得代码变得冗长且分散,容易遗漏。虽然
new(std::nothrow)
提供了类似
malloc
的返回
NULL
的行为,但那通常是用于特定场景,比如在内存极度紧张、不希望程序崩溃而是优雅降级的嵌入式系统或某些高性能库中。

最后,从C++特性融合度来看,

new
是C++标准库和现代C++编程范式的基石。智能指针(如
std::unique_ptr
std::shared_ptr
)就是基于
new
delete
机制来工作的,它们提供了自动化的内存管理,极大地减少了内存泄漏的风险。如果你坚持使用
malloc
,那么你就失去了享受这些现代C++便利的机会,你的代码可能会显得“C味”十足,与C++的生态格格不入。所以,除非有非常特殊且充分的理由,否则在C++中,
new
永远是管理堆内存的首选。

在哪些特定场景下,
malloc
在C++中仍然有其存在的价值?

虽然我们一直在强调

new
在C++中的优越性,但
malloc
也并非完全没有用武之地。在某些特定的、通常是更底层或者需要与C代码交互的场景下,
malloc
依然能发挥其作用,甚至可能是更合适的选择。

Noya
Noya

让线框图变成高保真设计。

下载

一个比较常见的场景是与C语言库进行交互。很多历史悠久的C库,它们内部的内存管理可能就是基于

malloc
free
的。如果你需要将C++程序与这样的C库集成,并且需要传递或接收由C库分配的内存,那么你可能就需要使用
malloc
来分配内存,以便C库能够正确地
free
掉它,或者反过来,由C库
malloc
的内存,你可能也需要用
free
来释放。这里存在一个“谁分配谁释放”的原则,通常是同一套机制分配的内存由同一套机制释放。混用
new/delete
malloc/free
会导致未定义行为。

另一个场景是自定义内存池或高性能内存管理。在一些对性能、内存碎片化有极高要求的系统中,开发者可能会选择实现自己的内存分配器(memory allocator)或内存池(memory pool)。这些自定义分配器在底层,很可能就是直接调用操作系统提供的内存分配接口(如Linux下的

mmap
或Windows下的
VirtualAlloc
),或者通过一次性
malloc
一大块内存,然后在这个大块内存上进行细粒度管理。在这种情况下,
malloc
提供了一个更原始、更接近系统调用的内存分配接口,它不涉及C++对象的构造和析构开销,这对于构建纯粹的、无额外负担的内存管理系统是很有利的。

此外,当你确实只需要一块原始的、未初始化的字节缓冲区时,

malloc
也可能是一个选项。比如,你可能在处理网络数据包、文件I/O缓冲区,或者实现一个序列化/反序列化机制,你需要的仅仅是一块连续的内存区域来存放字节数据,而不需要任何C++对象的构造/析构语义。在这种情况下,
malloc
可以提供更直接的内存分配,避免了
new
可能带来的额外开销(尽管这种开销通常很小)。不过,即便在这种情况下,现代C++也推荐使用
std::vector
或者
std::byte
来管理字节数组,它们提供了更好的RAII和边界检查。

还有,就是

realloc
的特殊性
malloc
家族里有一个
realloc
函数,它可以尝试在原地扩展或收缩已分配的内存块,或者在必要时移动到新位置。这对于某些需要动态调整大小的缓冲区(比如一个不断增长的C风格字符串缓冲区)可能很有用。然而,
realloc
与C++对象结合使用是非常危险的,因为它不会调用对象的构造函数或析构函数,直接移动或截断对象可能会导致严重的问题。所以,
realloc
的价值主要体现在处理原始数据块,而不是C++对象。

所以,虽然

new
是C++的首选,但
malloc
在与C代码互操作、构建底层内存管理系统以及处理原始字节数据等特定场景下,仍然有其不可替代的地位。关键在于,你要清楚地知道你在做什么,以及为什么选择它。

如何安全地处理
new
malloc
的内存分配失败?

内存分配失败,虽然在现代系统内存普遍充裕的情况下不那么常见,但一旦发生,后果通常是灾难性的。因此,安全地处理这种情况是编写健壮C++程序的关键。

对于

new
操作符,默认行为是当内存分配失败时,它会抛出
std::bad_alloc
异常。这是C++标准库中定义的一个异常类,专门用于指示内存分配失败。处理这种失败的标准方式就是使用
try-catch
块:

try {
    MyClass* obj = new MyClass();
    // 使用obj...
    delete obj;
} catch (const std::bad_alloc& e) {
    // 内存分配失败,这里处理错误,例如:
    std::cerr << "内存分配失败: " << e.what() << std::endl;
    // 可以在这里记录日志,或者尝试其他恢复策略,或者直接退出
    // exit(EXIT_FAILURE);
}

这种方式的好处在于,它将错误处理逻辑与正常的业务逻辑分离开来,使得代码更清晰。如果程序设计得当,异常可以沿着调用栈向上传播,直到被合适的处理器捕获。

另一种处理

new
分配失败的方式是使用
new(std::nothrow)
。这个版本在内存分配失败时不会抛出异常,而是返回
NULL
指针,行为上更类似于
malloc

MyClass* obj = new(std::nothrow) MyClass();
if (obj == nullptr) { // 或者 obj == NULL
    // 内存分配失败,处理错误
    std::cerr << "内存分配失败 (nothrow版本)" << std::endl;
    // ...
} else {
    // 成功分配,使用obj...
    delete obj;
}

选择哪种方式取决于你的程序设计哲学和错误处理策略。在大多数现代C++应用中,使用异常是更推荐的做法,因为它与C++的异常安全设计理念更吻合。

对于

malloc
函数,它的行为更直接:内存分配失败时,它会返回
NULL
指针。因此,处理
malloc
分配失败的唯一方式就是显式地检查返回值

int* arr = (int*)malloc(100 * sizeof(int));
if (arr == nullptr) { // 或者 arr == NULL
    // 内存分配失败,处理错误
    std::cerr << "malloc分配内存失败" << std::endl;
    // ...
} else {
    // 成功分配,使用arr...
    free(arr);
}

无论你使用

new
还是
malloc
,更高级、更推荐的做法是利用RAII(Resource Acquisition Is Initialization)原则和智能指针来管理动态内存。智能指针(如
std::unique_ptr
std::shared_ptr
)在内部封装了
new
delete
的调用,它们在对象生命周期结束时会自动释放内存,极大地减少了内存泄漏的风险。当
new
在智能指针内部抛出
std::bad_alloc
时,智能指针本身不会被构造,因此也不会有资源泄露的问题。

#include  // for std::unique_ptr

try {
    std::unique_ptr obj_ptr = std::make_unique(); // 推荐使用make_unique
    // 使用obj_ptr,无需手动delete
    // obj_ptr->someMethod();
} catch (const std::bad_alloc& e) {
    std::cerr << "智能指针创建失败,内存不足: " << e.what() << std::endl;
    // ...
}

使用智能指针,你通常不需要直接面对

new
malloc
的分配失败问题,因为这些问题会被底层机制处理,或者通过异常通知你。这使得代码更简洁,也更安全。所以,在C++中,避免直接管理裸指针,尽可能地使用智能指针,是处理内存分配失败的最佳实践。

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

379

2023.06.20

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

607

2023.07.25

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

348

2023.08.02

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

255

2023.08.09

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

583

2023.09.05

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

519

2023.09.20

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

631

2023.09.20

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

595

2023.09.22

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号