👋 欢迎阅读

🎯 欢迎来到「C++ 内存管理」深入讲解! 本文将带你从"程序运行时内存究竟如何划分"这一基础问题出发,逐步深入理解 C++ 内存分布、C 语言动态内存管理方式、C++ 内存管理方式、malloc/free 与 new/delete 的区别,以及内存泄漏的成因与防范。
在开始之前,建议你先:
-
了解背景知识:内存管理是 C/C++ 编程中至关重要的一环。不同于 Java、Python 等带有自动垃圾回收的语言,C++ 将内存管理的责任交给了程序员。理解内存的布局和动态分配方式,是写出健壮、高效、无内存泄漏代码的基础。
-
明确学习目标 :掌握以下五个核心知识点------
① C++ 内存分布 :理解 C++ 程序运行时内存的五大区域------栈 (局部变量、函数调用帧)、堆 (动态分配的内存)、数据段/静态区 (全局变量、静态变量)、常量区 (字符串常量、只读数据)以及代码段 (可执行指令),并了解各区域的特点与生命周期;
② C 语言动态内存管理方式 :掌握 C 语言中
malloc、calloc、realloc和free四个函数的用法、参数含义及返回值处理,理解它们各自的应用场景(如calloc会初始化零、realloc可调整已有内存大小);③ C++ 内存管理方式 :掌握 C++ 中
new和delete的用法,理解new会自动计算大小、调用构造函数 ,delete会调用析构函数 ,并熟悉new[]和delete[]在数组分配中的配对使用;④ malloc/free 和 new/delete 的区别 :从多个维度深入对比两者的差异------
new/delete是运算符 (可重载),malloc/free是函数 ;new会调用构造/析构函数,而malloc/free不会;new失败时抛异常,malloc失败时返回NULL;以及它们底层实际分配内存的机制异同;⑤ 内存泄漏 :理解内存泄漏的定义------已分配的内存不再使用且无法被释放
一.C/C++内存分布
我们先来看一段代码回答相关的问题
cpp
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
//1. 选择题:
//选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
//globalVar在哪里?____ staticGlobalVar在哪里?____
//staticVar在哪里?____ localVar在哪里?____
//num1 在哪里?____
//char2在哪里?____ *char2在哪里?___
//pChar3在哪里?____ *pChar3在哪里?____
//ptr1在哪里?____ *ptr1在哪里?____
说明
栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
内存映射段先不作了解后面会讲到。
堆用于程序运行时动态内存分配,堆是可以上增长的。
数据段--存储全局数据和静态数据。
代码段--可执行的代码/只读常量。
二.C 语言中动态内存管理方式: malloc/calloc/realloc/free
cpp
void Test ()
{
int* p1 = (int*) malloc(sizeof(int));
free(p1);
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}
说明
**malloc:**只管分配内存,不管内容;
**calloc:**在分配内存的同时自动清零
**realloc:**负责调整已分配内存的大小
**free:**用于释放空间
因此可以回答上面那个问题,不需要free,因为realloc相当于接手了p2指向的内存,你p2释放也是释放那个区域,你p3释放也是那个区域,因此不需要也不能否则就重复释放导致崩溃!
三.C++ 内存管理方式
3.1 new/delete操作内置类型
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过 new 和 delete 操作符进行动态内存管理。
格式:类型指针 变量 = new 类型对象个数{初始化}
格式:类型指针 变量 = new 类型(初始化)
cpp
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[10];
delete ptr4;
delete ptr5;
delete[] ptr6;
}

注意:申请和释放单个元素的空间,使用 new 和 delete 操作符,申请和释放连续的空间,使用 new\[\] 和 delete\[\],注意:匹配起来使用。
3.2 new/delete操作自定义类型
cpp
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
//最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A)*10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0
}
注意:在申请自定义类型的空间时, new 会调用构造函数, delete 会调用析构函数,而 malloc 与 free 不会。其实这也很好理解因为malloc和free出来的时候还没有类这个概念
3.3 operator new与operator delete函数
new 和 delete 是用户进行动态内存申请和释放的操作符 ,operator new 和 operator delete 是系统提供的 全局函数 ,new 在底层调用 operator new 全局函数来申请空间,delete 在底层通过 operator delete全局函数来释放空间。
而在其中operator new 实际也是通过malloc来申请空间 ,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete最终是通过free来释放空间的。
3.4 new和delete的实现原理
内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似。
自定义类型
new的原理
调用operator new函数申请空间
在申请的空间上执行构造函数,完成对象的构造
delete的原理
在空间上执行析构函数,完成对象中资源的清理工作
调用operator delete函数释放对象的空间
new N的原理
调用operator new\[\]函数,在operator new\[\]中实际调用operator new函数完成N个对象空间的申请
在申请的空间上执行N次构造函数
delete的原理\[\]
在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
调用operator delete\[\]释放空间,实际在operator delete\[\]中调用operator delete来释放空间
3.5 搭配不当
自定义类型:如果new和delete对应不当,程序会崩溃
内置类型:如果new和delete对应不当,程序不会崩溃但不建议使用
四.malloc/free和new/delete的区别(常见面试题)
共同点:都是从堆上申请空间,并且需要用户手动释放
不同点:
-
malloc和free是函数 ,new和delete是操作符
-
malloc申请的空间不会初始化 ,new会初始化
-
malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型可, 如果是多个对象,\[\]中指定对象个数即可
-
malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
-
malloc申请空间失败时,返回的是NULL,new申请空间失败时,报异常。(因为前者是函数,后者是操作符)
-
申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数 ,而new在申请空间后会调用构造函数完成对象的初始化 ,delete在释放空间前会调用析构函数完成空间中资源的清理
五.内存泄露
内存泄漏:因为疏忽或错误造成程序未能释放已经不再使用的内存的情况 。内存泄漏并不是指内存在物理上的消失 ,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏危害:内存泄漏会导致响应越来越慢,最终卡死。
cpp
void MemoryLeaks()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}
C/C++程序中一般我们关心两种方面的内存泄漏:
-
堆内存泄漏
-
系统资源泄漏
🎯 闭幕

🎉 恭喜你完成了 C++ 内存管理核心知识的学习!今天我们系统梳理了 C++ 内存分布 、C语言动态内存管理方式 、C++ 内存管理方式 、malloc/free 和 new/delete 的区别 以及 内存泄漏 这五大核心模块。
🚀 动手实践
在本地 IDE 或在线编译器中编写代码,尝试不同的测试用例,亲身体验每个知识点的用法。
💡 深入思考
-
C++ 内存分布 :C++ 中 栈、堆、数据段(静态区)、代码段 分别存储什么类型的数据?全局变量、静态变量、局部变量、常量字符串 各存储在哪个区域?
-
C语言动态内存管理方式 :
malloc、calloc、realloc的 区别 是什么?free如何知道要释放多大的内存? -
C++ 内存管理方式 :
new和delete的 执行流程 是什么?new[]和delete[]为什么必须配对使用? -
malloc/free 和 new/delete 的区别 :本质区别 是什么?能否混用 (如
malloc分配delete释放)?为什么? -
内存泄漏 :什么是内存泄漏 ?内存泄漏的危害 是什么?
📚 延伸挑战
-
分析以下代码中变量各存储在哪个内存区域:
int a; static int b; const char* p = "hello"; char arr[10]; int* heap = new int(10);。 -
分别使用
malloc/free和new/delete实现 动态数组的创建和释放,对比语法差异。 -
定义一个
Student类(包含name和age),分别用new创建单个对象和对象数组,并用delete正确释放。 -
分析以下代码的问题并修复:
int* p = new int[10]; // ... delete p;。
如果你觉得本文对你有所帮助,欢迎:
👍 点赞 / 收藏
👤 关注作者,获取更多题解
💬 留言交流你的疑问或优化思路
🔍 深入思考参考答案
-
C++ 内存分布 :栈 存储局部变量、函数参数、返回值等;堆 存储动态分配的内存(
malloc/new);数据段(静态区) 存储全局变量和静态变量(未初始化的在 BSS 段);代码段 存储可执行代码和常量(如常量字符串)。const修饰的局部变量在栈上,const修饰的全局变量在数据段的只读区。 -
C语言动态内存管理方式 :
malloc分配指定字节数的内存(不初始化),calloc分配并初始化为 0,realloc调整已分配内存的大小。free释放时,内存块头部记录了大小信息(由malloc实现维护)。 -
C++ 内存管理方式 :
new先调用operator new分配内存,再调用构造函数;delete先调用析构函数,再调用operator delete释放内存。new[]和delete[]必须配对,否则会导致未定义行为(可能只析构第一个对象)。 -
malloc/free 和 new/delete 的区别 :
malloc/free是 函数 ,new/delete是 运算符 ;new会调用构造函数,delete会调用析构函数,而malloc/free不会。不能混用 ,因为底层实现机制不同(如new可能重载,内存布局不同)。 -
内存泄漏 :内存泄漏指 动态分配的内存未被释放,导致内存占用持续增加,最终可能耗尽系统内存。
🛠️ 延伸挑战参考解法
1. 内存分布分析
cpp
/*
* 功能:分析 C++ 程序中不同变量的内存分布
* 重点:栈、堆、静态区、代码段各存储什么类型的数据
*/
#include <iostream>
using namespace std;
// 全局变量(未初始化)→ 数据段(BSS 段)
int g_global;
// 全局变量(已初始化)→ 数据段(数据区)
int g_init = 100;
// 全局常量 → 数据段(只读区)
const int g_const = 200;
// 静态全局变量 → 数据段(BSS 段)
static int g_static;
void TestMemoryDistribution()
{
// 局部变量 → 栈
int local_var;
static int local_static = 50; // 静态局部变量 → 数据段(数据区)
const int local_const = 30; // const 局部变量 → 栈(不是常量区!)
// 常量字符串 → 代码段(只读常量区)
const char* pStr = "hello world";
// 指针变量本身在栈上,指向的内存取决于分配方式
char arr[10] = "hello"; // arr 数组本身在栈上
// 动态分配的内存 → 堆
int* pHeap = new int(10);
delete pHeap;
}
int main()
{
cout << "=== 内存分布总结 ===" << endl;
cout << "栈:局部变量、函数参数、返回值" << endl;
cout << "堆:动态分配的内存(malloc/new)" << endl;
cout << "数据段(静态区):全局变量、静态变量" << endl;
cout << "代码段:可执行代码、常量字符串、const 全局变量" << endl;
cout << "\n注意:const 局部变量在栈上,不在常量区!" << endl;
TestMemoryDistribution();
return 0;
}
2. malloc/free vs new/delete 动态数组
cpp
/*
* 功能:对比 malloc/free 和 new/delete 的语法差异
* 场景:动态数组的创建和释放
* 重点:new/delete 是运算符,malloc/free 是函数
* new 会调用构造函数,delete 会调用析构函数
*/
#include <iostream>
#include <cstdlib> // malloc/free
using namespace std;
class Test
{
private:
int _val;
public:
Test(int val = 0)
: _val(val)
{
cout << "Test() constructed, val=" << _val << endl;
}
~Test()
{
cout << "~Test() destructed, val=" << _val << endl;
}
void Print() const
{
cout << "Test val: " << _val << endl;
}
};
int main()
{
cout << "=== malloc/free 分配(不调用构造/析构) ===" << endl;
// malloc 只分配内存,不调用构造函数
Test* p1 = (Test*)malloc(sizeof(Test) * 3);
// p1 指向的内存未初始化,调用 Print 可能出错
// p1[0].Print(); // 未定义行为!
free(p1); // free 只释放内存,不调用析构函数
cout << "\n=== new/delete 分配(调用构造/析构) ===" << endl;
// new 分配内存并调用构造函数
Test* p2 = new Test(100);
p2->Print(); // 对象已构造,安全调用
delete p2; // 先调用析构函数,再释放内存
cout << "\n=== new[]/delete[] 分配数组 ===" << endl;
// new[] 分配数组,每个元素都调用构造函数
Test* p3 = new Test[3]{10, 20, 30};
for (int i = 0; i < 3; i++)
{
p3[i].Print();
}
delete[] p3; // 每个元素都调用析构函数
return 0;
}
3. new 创建对象和对象数组
cpp
/*
* 功能:演示 new/delete 创建和释放单个对象及对象数组
* 重点:单个对象用 new/delete,数组用 new[]/delete[]
* 必须配对使用,否则导致未定义行为
*/
#include <iostream>
#include <cstring>
using namespace std;
class Student
{
private:
char* _name; // 动态分配的姓名
int _age;
public:
// 构造函数:为姓名分配堆内存
Student(const char* name, int age)
: _age(age)
{
_name = new char[strlen(name) + 1];
strcpy(_name, name);
cout << "Student \"" << _name << "\" constructed" << endl;
}
// 析构函数:释放姓名占用的堆内存
~Student()
{
cout << "Student \"" << _name << "\" destructed" << endl;
delete[] _name;
_name = nullptr;
}
void Print() const
{
cout << "Name: " << _name << ", Age: " << _age << endl;
}
};
int main()
{
cout << "=== 创建单个对象 ===" << endl;
// new 分配内存 + 构造
Student* s1 = new Student("Alice", 20);
s1->Print();
// delete 析构 + 释放内存
delete s1;
cout << "\n=== 创建对象数组 ===" << endl;
// new[] 分配内存 + 逐个构造
Student* sArr = new Student[3]
{
{"Bob", 21},
{"Charlie", 22},
{"David", 23}
};
for (int i = 0; i < 3; i++)
{
sArr[i].Print();
}
// delete[] 逐个析构 + 释放内存
delete[] sArr;
return 0;
}
4. new\[\] 和 delete 混用的错误分析
cpp
/*
* 功能:分析 new[] 和 delete 混用的错误
* 场景:new[] 分配数组,但使用 delete(而非 delete[])释放
* 重点:new[] 必须配 delete[],否则导致未定义行为
* 对于内置类型(如 int),可能不明显,但仍是错误用法
*/
#include <iostream>
using namespace std;
class Test
{
private:
int _val;
public:
Test(int val = 0)
: _val(val)
{
cout << "Test(" << _val << ") constructed" << endl;
}
~Test()
{
cout << "~Test() destructed, val=" << _val << endl;
}
};
int main()
{
cout << "=== 正确用法:new[] + delete[] ===" << endl;
Test* p1 = new Test[3]{1, 2, 3};
delete[] p1; // 正确:每个对象都调用析构函数
cout << endl;
cout << "=== 错误用法:new[] + delete(未定义行为) ===" << endl;
Test* p2 = new Test[3]{4, 5, 6};
// delete p2; // 错误!应该用 delete[]
// 使用 delete 替代 delete[] 会导致:
// 1. 只调用第一个对象的析构函数(其余未析构)
// 2. 内存释放时可能出错(数组内存布局不同)
// 取消注释并运行可能崩溃或行为异常
cout << "Warning: delete instead of delete[] is undefined behavior!" << endl;
// 为了演示正确性,这里使用正确的 delete[]
delete[] p2;
cout << "\n=== 内置类型也不应混用 ===" << endl;
int* p3 = new int[10];
// delete p3; // 虽然对内置类型可能不会崩溃,但仍是未定义行为
delete[] p3; // 正确用法
return 0;
}
祝你在 C++ 的学习之路上越走越稳,早日掌握更多编程技巧!下次见 🚀✨
说明