欢迎光临
我们一直在努力

从底层到实战:C++ 动态内存分配的三种方式

目录

一、为什么需要动态内存分配?

二、动态内存分配的基础

1. malloc:C 语言的基础动态内存分配

2. calloc:带初始化的 malloc

3. new:C++ 面向对象的内存分配

四、核心对比:malloc/calloc/new 的关键区别

六、进阶建议:什么时候用哪个?(适用场景)

七、总结


一、为什么需要动态内存分配?

        当你在 C++ 中定义数组时,如果数组大小需要运行时确定(比如根据用户输入),静态数组(int arr[10])就满足不了需求;或者你需要创建一个类对象,希望它的生命周期不受函数栈的限制 —— 这时候就需要动态内存分配。

        C++ 中动态内存分配有三种核心方式:C 语言风格的 malloc/calloc,以及 C++ 特有的 new。


二、动态内存分配的基础

  • 内存区域:动态内存分配的是堆内存(Heap),区别于栈内存(Stack):

    • 栈内存:由编译器自动分配 / 释放,生命周期随函数调用结束而结束;

    • 堆内存:由程序员手动申请 / 释放,生命周期由程序员控制(忘记释放会导致内存泄漏)。

  • 核心要求:申请的堆内存必须 “配对释放”——malloc/calloc 对应 free,new 对应 delete(数组用 delete[]),不能混用。


1. malloc:C 语言的基础动态内存分配

  • 定义:void* malloc(size_t size)

    • 功能:从堆中申请 size 字节的连续内存;

    • 返回值:成功返回指向内存的 void* 指针,失败返回 NULL;

    • 特点:只分配内存,不初始化(内存中是随机的垃圾值),需要手动类型转换。

  • 代码示例:

#include <iostream>
#include <cstdlib> // malloc/free 的头文件
using namespace std;

int main() {
// 申请4个int大小的内存(16字节)
int* arr = (int*)malloc(4 * sizeof(int));
if (arr == NULL) { // 必须检查是否申请失败
cerr << "内存申请失败" << endl;
return 1;
}

// 手动初始化(malloc不初始化,内存值是随机的)
for (int i = 0; i < 4; i++) {
arr[i] = i + 1;
}

// 输出:1 2 3 4
for (int i = 0; i < 4; i++) {
cout << arr[i] << " ";
}
cout << endl;

free(arr); // 释放内存,必须手动调用
arr = NULL; // 避免野指针(可选,但推荐)
return 0;
}

2. calloc:带初始化的 malloc

  • 定义:void* calloc(size_t num, size_t size)

    • 功能:从堆中申请 num 个大小为 size 的连续内存;

    • 返回值:成功返回 void* 指针,失败返回 NULL;

    • 特点:分配内存后自动初始化为 0,等价于 malloc(num*size) + memset(内存地址, 0, num*size)。

  • 代码示例:

#include <iostream>
#include <cstdlib>
using namespace std;

int main() {
// 申请4个int大小的内存,自动初始化为0
int* arr = (int*)calloc(4, sizeof(int));
if (arr == NULL) {
cerr << "内存申请失败" << endl;
return 1;
}

// 输出:0 0 0 0(calloc自动初始化)
for (int i = 0; i < 4; i++) {
cout << arr[i] << " ";
}
cout << endl;

free(arr);
arr = NULL;
return 0;
}

3. new:C++ 面向对象的内存分配

  • 定义:类型* 指针 = new 类型;(单个对象)/ 类型* 指针 = new 类型[数量];(数组)

    • 功能:不仅分配堆内存,还会自动调用构造函数(初始化对象);

    • 返回值:成功返回对应类型的指针,失败抛出 bad_alloc 异常(C++11 后可指定 nothrow 返回 NULL);

    • 特点:C++ 专属,支持面向对象,无需手动类型转换。

  • 代码示例(单个对象):

#include <iostream>
#include <new> // nothrow 头文件
using namespace std;

class Person {
public:
string name;
int age;
// 构造函数
Person(string n, int a) : name(n), age(a) {
cout << "Person构造函数被调用" << endl;
}
// 析构函数
~Person() {
cout << "Person析构函数被调用" << endl;
}
void show() {
cout << "姓名:" << name << ",年龄:" << age << endl;
}
};

int main() {
// 方式1:正常new,失败抛异常
try {
Person* p1 = new Person("张三", 20);
p1->show(); // 输出:姓名:张三,年龄:20
delete p1; // 释放内存,自动调用析构函数
} catch (bad_alloc& e) {
cerr << "内存申请失败:" << e.what() << endl;
}

// 方式2:nothrow版new,失败返回NULL
Person* p2 = new(nothrow) Person("李四", 25);
if (p2 != NULL) {
p2->show();
delete p2;
}

return 0;
}

代码示例(数组):

#include <iostream>
using namespace std;

int main() {
// 申请int数组,未初始化(new数组不会自动初始化内置类型)
int* arr1 = new int[4];
for (int i = 0; i < 4; i++) {
arr1[i] = i + 1;
cout << arr1[i] << " "; // 输出:1 2 3 4
}
cout << endl;
delete[] arr1; // 数组必须用delete[]释放

// 申请int数组并初始化(C++11支持)
int* arr2 = new int[4]{10, 20, 30, 40};
for (int i = 0; i < 4; i++) {
cout << arr2[i] << " "; // 输出:10 20 30 40
}
cout << endl;
delete[] arr2;

return 0;
}

四、malloc/calloc/new 的关键区别

特性malloccallocnew
所属语言 C/C++ C/C++ 仅 C++
初始化 不初始化(垃圾值) 自动初始化为 0 内置类型:不初始化;类对象:调用构造函数
返回值类型 void*(需手动强转) void*(需手动强转) 对应类型指针(无需强转)
失败处理 返回 NULL 返回 NULL 默认抛 bad_alloc 异常,可指定 nothrow 返回 NULL
面向对象支持 不支持(仅分配内存) 不支持(仅分配内存) 支持(调用构造 / 析构函数)
释放方式 free() free() delete(单个对象)/delete [](数组)
语法形式 malloc (字节数) calloc (个数,单个大小) new 类型 /new 类型 [个数]

六、适用场景

  • 纯 C 项目:用 malloc/calloc(calloc 适合需要初始化为 0 的场景);

  • C++ 项目(面向过程):简单内置类型分配,malloc/new 均可,但优先 new(更符合 C++ 风格);

  • C++ 项目(面向对象):必须用 new(需要调用构造 / 析构函数);

  • 需要自动初始化 0:用 calloc(C)或 new 类型(0)/new 类型[数]{0}(C++);

  • 现代 C++ 开发:优先使用智能指针(std::unique_ptr/std::shared_ptr)替代裸指针的 new,避免手动管理内存。


  • 七、总结

  • 配对使用:malloc/calloc ↔ free,new ↔ delete,new[] ↔ delete[];

  • C++ 类对象必须用 new/delete,否则析构函数无法执行;

  • 现代 C++ 优先用智能指针,减少手动内存管理的坑。

  • 赞(0)
    未经允许不得转载:171主机测评 » 从底层到实战:C++ 动态内存分配的三种方式
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址