
目录
[(一)C/C++ 的内存分布](#(一)C/C++ 的内存分布)
[1. 基础概念解释](#1. 基础概念解释)
[2. 代码例子演示](#2. 代码例子演示)
[【示例 1:不同变量的内存位置验证】](#【示例 1:不同变量的内存位置验证】)
[(二)C 语言中的动态内存管理](#(二)C 语言中的动态内存管理)
[1. 基础概念解释](#1. 基础概念解释)
[2. 代码例子演示](#2. 代码例子演示)
[【示例 2:malloc 的基本用法】](#【示例 2:malloc 的基本用法】)
[【示例 3:calloc 的用法 - 自动初始化】](#【示例 3:calloc 的用法 - 自动初始化】)
[【示例 4:realloc 的用法 - 调整内存大小】](#【示例 4:realloc 的用法 - 调整内存大小】)
[(三)C++ 中的内存管理(new、delete)](#(三)C++ 中的内存管理(new、delete))
[1. 基础概念解释](#1. 基础概念解释)
[2. 代码例子演示](#2. 代码例子演示)
[【示例 5:new、delete 操作内置类型】](#【示例 5:new、delete 操作内置类型】)
[【示例 6:new、delete 操作自定义类型】](#【示例 6:new、delete 操作自定义类型】)
[(四)operator new 与 operator delete 函数](#(四)operator new 与 operator delete 函数)
[1. 基础概念解释](#1. 基础概念解释)
[2. 代码例子演示](#2. 代码例子演示)
[【示例 7:operator new/delete 的直接使用】](#【示例 7:operator new/delete 的直接使用】)
[(五)new 与 delete 的实现原理](#(五)new 与 delete 的实现原理)
[1. 基础概念解释](#1. 基础概念解释)
[内置类型(int、double 等):](#内置类型(int、double 等):)
[2. 代码例子演示](#2. 代码例子演示)
[【示例 8:new/delete 原理验证】](#【示例 8:new/delete 原理验证】)
[(六)malloc/free 和 new/delete 的区别](#(六)malloc/free 和 new/delete 的区别)
[1. 基础概念解释](#1. 基础概念解释)
[2. 代码例子演示](#2. 代码例子演示)
[【示例 9:对比演示 - 错误处理方式】](#【示例 9:对比演示 - 错误处理方式】)
前言
大家好,我是你们的小小的风呀!今天是【从零开始学 C++】专题的第五篇,我们来聊聊一个非常重要但又容易让新手头疼的话题 ------内存管理。
为什么要学内存管理?简单说:
-
程序运行的本质就是在操作内存
-
懂内存管理才能写出高效、稳定的程序
-
这是面试必考题!面试官最爱问的就是 malloc 和 new 的区别
-
很多 bug(内存泄漏、野指针)都源于对内存管理的不理解
别担心,今天我用大白话 + 代码示例,带你彻底搞懂内存管理!坐稳扶好,我们发车~
(一)C/C++ 的内存分布
1. 基础概念解释
首先,我们要明白:程序运行时,内存不是乱存的,而是有严格的区域划分!
想象一下,一个程序就像一栋大楼,不同楼层有不同功能:
-
内核空间:大楼的顶层,操作系统用的,我们用户代码碰不到
-
栈(Stack):像一个桶,从上面往下放东西(向下增长),放局部变量、函数参数
-
内存映射段:中间层,用来加载动态库、文件映射,了解就行
-
堆(Heap):像一个箱子,从下往上放东西(向上增长),我们手动申请的动态内存都在这
-
数据段:放全局变量和静态变量,程序运行期间一直存在
-
代码段:最底层,放我们写的代码(编译后的机器指令)和字符串常量
一张图让你秒懂:

新手必记口诀:
-
栈向下,堆向上,他俩对着长
-
局部变量在栈上,动态申请在堆上
-
全局静态数据段,代码常量代码段
2. 代码例子演示
【示例 1:不同变量的内存位置验证】
这个例子帮你直观感受不同变量存在哪里。
cpp
#include <iostream>
using namespace std;
// 全局变量 - 数据段
int global_var = 100;
// 静态全局变量 - 数据段
static int static_global_var = 200;
void test(int param) // param是函数参数 - 栈
{
// 静态局部变量 - 数据段
static int static_local_var = 300;
// 局部变量 - 栈
int local_var = 400;
// 数组 - 栈
int arr[5] = {1, 2, 3, 4, 5};
// 字符串常量本身 - 代码段(常量区)
// char2数组本身 - 栈
char char2[] = "hello";
// pChar3指针变量本身 - 栈
// 指向的字符串"world" - 代码段(常量区)
const char* pChar3 = "world";
// ptr指针本身 - 栈
// 指向的动态内存 - 堆
int* ptr = (int*)malloc(sizeof(int) * 4);
cout << "=== 各变量内存地址观察 ===" << endl;
cout << "全局变量: " << &global_var << endl;
cout << "静态全局变量: " << &static_global_var << endl;
cout << "静态局部变量: " << &static_local_var << endl;
cout << "函数参数: " << ¶m << endl;
cout << "局部变量: " << &local_var << endl;
cout << "局部数组: " << arr << endl;
cout << "char2数组: " << (void*)char2 << endl;
cout << "pChar3指针本身: " << (void*)&pChar3 << endl;
cout << "pChar3指向内容: " << (void*)pChar3 << endl;
cout << "ptr指针本身: " << (void*)&ptr << endl;
cout << "ptr指向的堆内存: " << (void*)ptr << endl;
free(ptr);
}
int main()
{
test(666);
return 0;
}
运行观察要点:
-
全局、静态变量的地址很接近,都在数据段
-
局部变量、参数的地址也很接近,都在栈上(地址比较大)
-
malloc 出来的地址在另一个区域(堆)
-
字符串常量 "world" 的地址很小,在代码段
例题练习:
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在哪里?___
答案:
cpp
答案及解析
1. C(数据段/静态区) ------ 全局变量默认存储在静态区,程序整个生命周期都存在。
2. C(数据段/静态区) ------ 静态全局变量同样存储在静态区,仅当前文件可见。
3. C(数据段/静态区) ------ 静态局部变量即使在函数内,也存储在静态区,只初始化一次,生命周期贯穿程序运行。
4. A(栈) ------ 普通局部变量,函数调用时在栈上分配,函数结束自动释放。
5. A(栈) ------ 局部数组属于局部变量,内存分配在栈上。
6. A(栈) ------ 局部数组名是栈上数组的首地址,数组整体存储在栈中。
7. A(栈) ------ 数组的第一个元素,和数组一起存储在栈中。
8. A(栈) ------ 指针变量本身是局部变量,存储在栈上。
9. D(代码段/常量区) ------ 指向的字符串常量`"abcd"`是只读常量,存储在常量区。
10.A(栈) ------ 指针变量本身是局部变量,存储在栈上。
11.B(堆) ------ `malloc`动态分配的内存,由程序员手动管理,存储在堆区。
(二)C 语言中的动态内存管理
C 语言提供了 4 个函数来管理动态内存:malloc、calloc、realloc、free。记住:这四个函数都是在堆上操作的!
1. 基础概念解释
先给大家一个大白话总结:
|-------------|--------|-----------------|
| 函数 | 作用 | 特点 |
| malloc | 申请内存 | 只申请,不初始化,里面是垃圾值 |
| calloc | 申请内存 | 申请 + 自动初始化为 0 |
| realloc | 调整内存大小 | 对已有内存扩容 / 缩容 |
| free | 释放内存 | 把申请的内存还给系统 |
新手常见误区:
-
❌ 忘记 free → 内存泄漏
-
❌ free 后继续使用指针 → 野指针
-
❌ 重复 free 同一块内存 → 程序崩溃
-
❌ free 不是 malloc 来的指针 → 程序崩溃
2. 代码例子演示
【示例 2:malloc 的基本用法】
malloc = memory + allocate,就是 "分配内存" 的意思。
cpp
#include <iostream>
#include <stdlib.h> // 要包含这个头文件!
using namespace std;
int main()
{
cout << "=== malloc基本使用 ===" << endl;
// 申请1个int的空间(4字节)
// malloc返回void*,需要强转成我们需要的类型
int* p1 = (int*)malloc(sizeof(int));
// 【重要】malloc可能失败!一定要检查!
if (p1 == NULL)
{
cout << "内存申请失败!" << endl;
return -1;
}
// malloc申请的空间里是随机值(垃圾值)
cout << "malloc后未初始化的值: " << *p1 << endl;
// 我们自己赋值
*p1 = 100;
cout << "赋值后的值: " << *p1 << endl;
// 申请10个int的空间(数组)
int* p2 = (int*)malloc(sizeof(int) * 10);
if (p2 != NULL)
{
// 像数组一样使用
for (int i = 0; i < 10; i++)
{
p2[i] = i * 10;
}
cout << "malloc数组内容: ";
for (int i = 0; i < 10; i++)
{
cout << p2[i] << " ";
}
cout << endl;
}
// 【重要】用完一定要释放!
free(p1);
free(p2);
// 【好习惯】free后把指针置空,避免野指针
p1 = NULL;
p2 = NULL;
cout << "内存已释放!" << endl;
return 0;
}
【示例 3:calloc 的用法 - 自动初始化】
calloc 会把申请的内存自动清 0,懒人福音!
cpp
#include <iostream>
#include <stdlib.h>
using namespace std;
int main()
{
cout << "=== calloc的使用 ===" << endl;
// calloc(个数, 每个的大小)
// 申请5个int,每个自动初始化为0
int* p = (int*)calloc(5, sizeof(int));
if (p != NULL)
{
cout << "calloc自动初始化后的值: ";
for (int i = 0; i < 5; i++)
{
cout << p[i] << " "; // 输出: 0 0 0 0 0
}
cout << endl;
// 当然我们也可以改值
p[0] = 10;
p[1] = 20;
cout << "修改后的值: ";
for (int i = 0; i < 5; i++)
{
cout << p[i] << " "; // 输出: 10 20 0 0 0
}
cout << endl;
}
free(p);
p = NULL;
return 0;
}
malloc vs calloc 对比:
-
malloc 更快,只申请不初始化
-
calloc 稍慢,但帮你清 0 了,更安全
【示例 4:realloc 的用法 - 调整内存大小】
realloc = re + allocate,就是 "重新分配"。比如原来申请了 4 个字节,现在不够用了,要变成 8 个字节。
cpp
#include <iostream>
#include <stdlib.h>
using namespace std;
int main()
{
cout << "=== realloc的使用 ===" << endl;
// 先申请2个int空间
int* p = (int*)malloc(sizeof(int) * 2);
if (p == NULL) return -1;
p[0] = 10;
p[1] = 20;
cout << "原来的空间(2个int): " << p[0] << " " << p[1] << endl;
cout << "原来的地址: " << (void*)p << endl;
// 不够用了!扩容到5个int
int* new_p = (int*)realloc(p, sizeof(int) * 5);
// 【重要】realloc可能返回新地址!
// 所以要用新指针接收,不要直接覆盖p
if (new_p != NULL)
{
p = new_p; // 扩容成功,更新指针
cout << "扩容后的地址: " << (void*)p << endl;
cout << "扩容后原来的数据还在: " << p[0] << " " << p[1] << endl;
// 新空间可以继续用
p[2] = 30;
p[3] = 40;
p[4] = 50;
cout << "扩容后全部数据: ";
for (int i = 0; i < 5; i++)
{
cout << p[i] << " ";
}
cout << endl;
}
// realloc也可以缩小空间(不常用)
p = (int*)realloc(p, sizeof(int) * 3);
cout << "缩容后只剩3个空间了" << endl;
free(p);
p = NULL;
return 0;
}
realloc 的两种情况:
-
原地扩容:原来的空间后面还有空位,直接扩大,地址不变
-
异地扩容:原来的空间后面满了,找个新地方,把数据拷过去,释放旧空间,返回新地址
(三)C++ 中的内存管理(new、delete)
C 语言的内存管理在 C++ 中可以继续用,但 C++ 发明了更好用的:new 和 delete 操作符!
1. 基础概念解释
为什么 C++ 要搞 new/delete?因为 C++ 有类啊!malloc 只会开空间,不会调用构造函数,这怎么行?
new 和 delete 的优势:
-
✅ 不用手动计算大小,自动算
-
✅ 不用强转返回值,自动匹配类型
-
✅ 可以初始化
-
✅ 对自定义类型,自动调用构造 / 析构函数
-
✅ 失败抛异常,不用判空(更现代的错误处理)
2. 代码例子演示
【示例 5:new、delete 操作内置类型】
先看对 int、double 这些基本类型怎么用。
cpp
#include <iostream>
using namespace std;
int main()
{
cout << "=== new/delete操作内置类型 ===" << endl;
// 1. 申请单个int空间
int* p1 = new int;
*p1 = 100;
cout << "单个int: " << *p1 << endl;
delete p1; // 释放
// 2. 申请+初始化
int* p2 = new int(200); // 括号里是初始值
cout << "申请+初始化: " << *p2 << endl;
delete p2;
// 3. 申请数组(10个int)
int* p3 = new int[10]; // 中括号!
for (int i = 0; i < 10; i++)
{
p3[i] = i * 10;
}
cout << "数组: ";
for (int i = 0; i < 10; i++)
{
cout << p3[i] << " ";
}
cout << endl;
delete[] p3; // 【重要】数组要用delete[]!
// 4. C++11新特性:数组初始化
int* p4 = new int[5]{1, 2, 3, 4, 5};
cout << "C++11数组初始化: ";
for (int i = 0; i < 5; i++)
{
cout << p4[i] << " ";
}
cout << endl;
delete[] p4;
cout << "全部释放完成!" << endl;
return 0;
}
重要提醒:
-
new 单个对象 → delete
-
new 数组 → delete []
-
一定要匹配使用! 不匹配会出问题!
【示例 6:new、delete 操作自定义类型】
这才是 new/delete 真正厉害的地方 ------ 自动调用构造 / 析构函数!
cpp
#include <iostream>
using namespace std;
// 定义一个简单的类
class Person
{
public:
// 构造函数
Person(string name = "无名", int age = 0)
: _name(name), _age(age)
{
cout << "构造函数调用: " << _name << "," << _age << "岁" << endl;
}
// 析构函数
~Person()
{
cout << "析构函数调用: " << _name << "拜拜了~" << endl;
}
void show()
{
cout << _name << "," << _age << "岁" << endl;
}
private:
string _name;
int _age;
};
int main()
{
cout << "===== 用malloc的情况 =====" << endl;
// malloc只会开空间,不会调用构造函数!
Person* p1 = (Person*)malloc(sizeof(Person));
// p1指向的空间里是垃圾值,不是真正的对象!
free(p1); // free也不会调用析构函数!
cout << endl << "===== 用new的情况 =====" << endl;
// new = 开空间 + 调用构造函数
Person* p2 = new Person("张三", 18);
p2->show();
// delete = 调用析构函数 + 释放空间
delete p2;
cout << endl << "===== new对象数组 =====" << endl;
// new数组 = 开N个空间 + 调用N次构造函数
Person* p3 = new Person[3]{
Person("小明", 10),
Person("小红", 11),
Person("小刚", 12)
};
// delete[] = 调用N次析构函数 + 释放空间
delete[] p3;
return 0;
}
运行结果对比:
cpp
===== 用malloc的情况 =====
===== 用new的情况 =====
构造函数调用: 张三,18岁
张三,18岁
析构函数调用: 张三拜拜了~
===== new对象数组 =====
构造函数调用: 小明,10岁
构造函数调用: 小红,11岁
构造函数调用: 小刚,12岁
析构函数调用: 小刚拜拜了~
析构函数调用: 小红拜拜了~
析构函数调用: 小明拜拜了~
-
malloc/free:静悄悄,什么都不打印(没调用构造 / 析构)
-
new/delete:有构造有析构,对象真正被创建和销毁了
这就是为什么 C++ 推荐用 new/delete!因为我们要的是对象,不是一块光秃秃的内存!
(四)operator new 与 operator delete 函数
1. 基础概念解释
很多新手会搞混:new 和 operator new 不是一回事!
给大家理清楚:
-
new:是 C++ 的关键字(操作符),我们写代码用的
-
operator new:是一个全局函数,new 在底层会调用它
调用关系:
cpp
我们写 new Person
↓ 底层调用
operator new函数
↓ 底层调用
malloc函数
operator new 的作用:
-
本质就是封装了 malloc
-
malloc 失败返回 NULL,operator new 失败会抛异常(bad_alloc)
-
这就是为什么 new 不需要判空的原因
同理:
-
delete 底层调用 operator delete
-
operator delete 底层调用 free
2. 代码例子演示
【示例 7:operator new/delete 的直接使用】
其实我们也可以直接调用这两个全局函数,虽然平时很少这么做。
cpp
#include <iostream>
using namespace std;
class Test
{
public:
Test() { cout << "构造函数" << endl; }
~Test() { cout << "析构函数" << endl; }
};
int main()
{
cout << "===== operator new只开空间,不构造 =====" << endl;
// operator new 只开空间,不调用构造函数!
Test* p = (Test*)operator new(sizeof(Test));
// 现在p指向的只是空间,还不是真正的对象!
cout << "===== 手动调用构造函数(定位new) =====" << endl;
// 如果要构造,需要手动调用
new(p) Test; // 定位new语法,在已有的空间上构造对象
cout << "===== 手动调用析构函数 =====" << endl;
p->~Test(); // 析构函数可以手动调用
cout << "===== operator delete只释放空间,不析构 =====" << endl;
operator delete(p); // 只释放空间
// 对比:new = operator new + 调用构造
// delete = 调用析构 + operator delete
return 0;
}
划重点:
-
operator new = 只开空间(封装 malloc)
-
operator delete = 只释放空间(封装 free)
-
真正的 new/delete,还要加上构造 / 析构的调用!
(五)new 与 delete 的实现原理
1. 基础概念解释
现在我们来揭开 new/delete 的神秘面纱,看看它到底是怎么工作的。
分两种情况:内置类型 和自定义类型
内置类型(int、double 等):
-
new:调用 operator new → 调用 malloc
-
delete:调用 operator delete → 调用 free
-
其实和 malloc/free 差不多,只是错误处理方式不同
自定义类型(类对象):
new 的执行流程:
-
调用 operator new 申请空间
-
在申请的空间上调用构造函数
-
返回对象的地址
delete 的执行流程:
-
对对象调用析构函数
-
调用 operator delete 释放空间
new T [N] 数组的执行流程:
-
调用 operator new [] → 调用 operator new → 调用 malloc
-
调用 N 次构造函数
-
(偷偷多存了 4 字节,记录数组大小 N)
delete [ ] 的执行流程:
-
从偷偷存的地方取出 N,调用 N 次析构函数
-
调用 operator delete [] → 调用 operator delete → 调用 free
2. 代码例子演示
【示例 8:new/delete 原理验证】
我们通过打印来观察调用顺序。
cpp
#include <iostream>
using namespace std;
class MyClass
{
public:
MyClass(int x = 0) : _x(x)
{
cout << "构造函数: x = " << _x << ", 地址: " << this << endl;
}
~MyClass()
{
cout << "析构函数: x = " << _x << ", 地址: " << this << endl;
}
private:
int _x;
};
int main()
{
cout << "========== 单个对象 new/delete ==========" << endl;
cout << "准备new..." << endl;
MyClass* p1 = new MyClass(100);
cout << "new完成,准备delete..." << endl;
delete p1;
cout << "delete完成" << endl;
cout << endl << "========== 数组 new[]/delete[] ==========" << endl;
cout << "准备new[] 3个对象..." << endl;
MyClass* p2 = new MyClass[3]{1, 2, 3};
cout << "new[]完成,准备delete[]..." << endl;
delete[] p2;
cout << "delete[]完成" << endl;
// 【错误示范】new[] 用 delete 释放(不用delete[])
// cout << endl << "========== 错误示范!new[] 用 delete ==========" << endl;
// MyClass* p3 = new MyClass[2]{10, 20};
// delete p3; // 只调用1次析构!内存泄漏!
return 0;
}
运行观察:
-
new 一个对象:先构造,返回地址
-
delete 一个对象:先析构,再释放
-
new [] 数组:3 次构造按顺序来
-
delete [] 数组:3 次析构按反序来(栈的特点)
(六)malloc/free 和 new/delete 的区别
1. 基础概念解释
这是面试必考题!我给大家整理了一张对比表,背下来面试直接满分!
|-----------|--------------|---------------------------|
| 对比维度 | malloc/free | new/delete |
| 身份 | C 语言的库函数 | C++ 的操作符(关键字) |
| 初始化 | 只申请空间,不初始化 | new 可以初始化(括号传参) |
| 大小计算 | 手动算字节数 | 自动计算(跟类型走) |
| 返回值 | void*,需要强转 | 直接返回对应类型指针 |
| 失败处理 | 返回 NULL,必须判空 | 抛异常 bad_alloc,需要捕获 |
| 自定义类型 | 只开空间,不调构造析构 | 开空间 + 调构造 / 析构 |
| 数组处理 | 手动计算大小 | new []/delete [] 专门处理 |
| 可重载吗? | 不能重载 | operator new/delete 可以重载 |
| 分配位置 | 堆 | 堆(自由存储区) |
一句话总结:
-
malloc/free 是 C 语言的老古董,简单粗暴
-
new/delete 是 C++ 的升级版,专为面向对象设计,更安全更强大
2. 代码例子演示
【示例 9:对比演示 - 错误处理方式】
cpp
#include <iostream>
#include <stdlib.h>
using namespace std;
int main()
{
cout << "===== malloc失败返回NULL =====" << endl;
// 申请一个超大空间,故意让它失败
int* p1 = (int*)malloc(1024 * 1024 * 1024 * 100); // 100GB!
if (p1 == NULL)
{
cout << "malloc失败了!返回NULL" << endl;
}
else
{
free(p1);
}
cout << endl << "===== new失败抛异常 =====" << endl;
try
{
// 同样申请超大空间
int* p2 = new int[1024 * 1024 * 1024 * 100];
// 如果成功才会走到这
delete[] p2;
}
catch (const bad_alloc& e)
{
cout << "new失败了!抛出异常: " << e.what() << endl;
}
cout << endl << "程序正常结束~" << endl;
return 0;
}
总结
今天的内容有点多,给大家划个重点:
-
内存分布:栈向下,堆向上,全局静态数据段,代码常量代码段
-
C 语言四件套:malloc(申请)、calloc(申请 + 清 0)、realloc(调整)、free(释放)
-
C++ 新方式:new/delete,比 C 的好用,支持自定义类型
-
底层原理:new 底层调用 operator new,operator new 底层调用 malloc
-
面试必背:malloc 和 new 的 6 大区别
新手建议:
-
C++ 中尽量用 new/delete,少用 malloc/free
-
一定要记得释放内存!养成好习惯
-
new 和 delete 要匹配,new [] 和 delete [] 要匹配
下期预告
下一篇我们来讲讲C++ 模板,初步了解STL
好啦,如果你想了解更多C++的内容记得关注我哦,【从零开始学 C++】系列持续更新中!
如果这篇文章对你有帮助,别忘了点赞 + 收藏 + 评论三连!有问题评论区见~