(1)new创建一个数字以及delete回收内存的使用:
cpp
#include <iostream>
using namespace std;
int* func()
{
int* pa = new int(10);
return pa;
}
void test01()
{
int* pf = func();
cout << *pf << endl;
}
int main()
{
test01();
return 0;
}

这段代码展示了 C++ 中动态内存分配 的基本用法,但有一个容易被忽视的问题------内存泄漏。下面我会逐步讲解代码,并指出需要注意的地方。
1. 包含头文件和命名空间
cpp
#include <iostream>
using namespace std;
-
#include <iostream>用于输入输出,这里主要用到cout。 -
using namespace std;让我们可以直接写cout,而不用写成std::cout。
2. func() 函数------动态分配单个 int
cpp
int* func()
{
int* pa = new int(10);
return pa;
}
-
new int(10)在堆(heap) 上申请了一块内存,用来存放一个int,并初始化为10。 -
该内存的地址被保存在局部指针
pa中,然后作为函数返回值返回。 -
关键点 :堆上的内存不会自动释放,需要程序员手动管理(用
delete释放)。
3. test01() 函数------使用动态分配的 int
cpp
void test01()
{
int* pf = func(); // 接收动态内存的地址
cout << *pf << endl; // 解引用,输出值 10
// 注意:这里缺少 delete pf; 会导致内存泄漏!
}
-
int* pf = func();拿到堆上int的地址。 -
*pf解引用,输出该内存中存储的值(10)。 -
严重问题 :函数结束后,指针
pf被销毁,但堆上的int没有被释放。从此再也无法访问或释放那块内存,造成内存泄漏。
4. main() 函数入口
cpp
int main()
{
test01();
return 0;
}
- 调用
test01(),执行完毕后程序结束。虽然程序退出时操作系统会回收所有内存,但良好的编程习惯要求我们显式释放动态分配的内存。
关键知识点总结
| 操作/概念 | 说明 |
|---|---|
new int(10) |
在堆上分配一个 int 并初始化为 10,返回该内存的地址 |
| 指针接收 | int* pf = func(); 让 pf 指向堆上的内存 |
| 解引用输出 | *pf 获取指针指向的值 |
| 内存泄漏 | 没有 delete pf;,导致堆内存无法释放,程序运行期间一直占用 |
| 正确做法 | 使用完动态内存后务必 delete,例如:delete pf; pf = nullptr; |
正确版本示例
cpp
void test01()
{
int* pf = func();
cout << *pf << endl;
delete pf; // 释放内存
pf = nullptr; // 可选:置空防止误用
}
为什么要避免内存泄漏?
-
长期运行的程序(如服务器、游戏)如果不断泄漏内存,最终会耗尽系统内存,导致崩溃。
-
养成良好的内存管理习惯,是写出健壮 C++ 程序的基础。
希望这篇讲解能帮你的读者理解动态内存分配的基本用法和潜在陷阱。如果有其他问题,欢迎继续探讨!
delete删除数字:
cpp
//new创建一个数字
#include <iostream>
using namespace std;
int* func()
{
int* pa = new int(10);
return pa;
}
void test01()
{
int* pf = func();
cout << "删除前:" <<* pf << endl;
delete pf;
cout << "删除后:" << *pf << endl;//error! 内存已经被回收,此时为非法访问
}
int main()
{
test01();
return 0;
}

这段代码用最简单的例子展示了 C++ 动态内存分配和释放的全过程,同时故意制造了一个经典错误------在释放内存后继续使用指针(悬垂指针)。下面我们来逐步解析。
1. 整体结构
-
func()函数:在堆上创建一个int并初始化为10,返回其地址。 -
test01()函数:调用func()获得指针,输出值,然后释放内存,再次输出已释放内存的值(错误操作)。 -
main():调用test01()。
2. 逐行解释
func() 函数
cpp
int* pa = new int(10);
-
new int(10)在堆(heap)上申请一块能存放int的内存,并将值设为10。 -
这块内存的地址保存在指针
pa中,然后作为返回值传回。
test01() 函数
cpp
int* pf = func();
cout << "删除前:" << *pf << endl; // ✅ 正常访问,输出 10
-
指针
pf指向堆上的int。 -
*pf解引用,输出该内存的值。
cpp
delete pf;
delete pf;释放pf指向的堆内存。内存归还给系统,pf变成悬垂指针(指向无效内存)。
cpp
cout << "删除后:" << *pf << endl; // ❌ 错误!访问已释放的内存
-
再次通过
*pf访问内存,此时行为是未定义的(Undefined Behavior)。 -
可能输出原来的值(内存未被覆盖),也可能输出乱码,或者直接导致程序崩溃。
3. 错误分析:悬垂指针与未定义行为
-
悬垂指针:指向已被释放的内存的指针。
-
对悬垂指针解引用会导致:
-
读取到不可预料的值(有时侥幸是旧值,但绝不可依赖);
-
写入可能破坏其他数据或导致程序崩溃;
-
极端情况下可能引发安全漏洞。
-
代码中虽然先输出再释放看起来没问题,但释放后再次访问就是典型的"use-after-free"漏洞。
4. 正确做法
使用完动态内存后:
-
及时释放 (
delete)。 -
立即将指针置为
nullptr,避免误用。
cpp
delete pf;
pf = nullptr; // 置空后,再次解引用会明确崩溃(容易调试)
// 后续若访问 *pf 会因空指针而报错,而不是悄无声息地未定义
- 不要在释放后继续使用指针。
5. 补充:现代 C++ 建议
在实际项目中,更推荐使用智能指针 (如 std::unique_ptr)来自动管理内存,避免手动 delete 的烦恼和风险。
cpp
#include <memory>
std::unique_ptr<int> func() {
return std::make_unique<int>(10);
}
// 离开作用域自动释放
总结
| 步骤 | 状态 | 说明 |
|---|---|---|
new int(10) |
分配内存,值为 10 | 堆内存可用 |
第一次 *pf |
正常读取 | 输出 10 |
delete pf |
内存已释放 | pf 变为悬垂指针 |
第二次 *pf |
未定义行为 | 禁止!可能导致各种奇怪问题 |
记住:释放内存后,原指针就成了"定时炸弹",一定不要再用它访问数据!
(2)new创建一个数组以及delete回收内存的使用:
cpp
#include <iostream>
using namespace std;
int* func()
{
int* arr = new int[10];
return arr;
}
void test03()
{
int* parr = func();
//释放
delete[] parr;
for (int i = 0;i < 10;i++)
{
parr[i] = i + 1000;
}
for (int i = 0;i < 10;i++)
{
cout << parr[i] << endl;
}
}
int main()
{
test03();
return 0;
}

这段代码演示了 C++ 动态内存管理中一个非常典型的错误------在释放内存后继续使用指针(即"悬垂指针"问题)。下面我会先解释代码原本想做什么,然后重点分析错误发生的位置及其后果,最后给出正确的写法建议。
1. 代码结构回顾
-
func():在堆上分配一个能存放 10 个int的数组,并返回其首地址。 -
test03():调用func()获得指针,然后......(这里出现了顺序错误) -
main():调用test03()。
2. 错误的顺序:先释放,后使用
cpp
void test03()
{
int* parr = func(); // 1. 获得动态数组
//释放
delete[] parr; // 2. 立刻释放数组!⚠️ 此时 parr 指向的内存已交还给系统
for (int i = 0;i < 10;i++)
{
parr[i] = i + 1000; // 3. 向已释放的内存写入数据 → 危险!
}
for (int i = 0;i < 10;i++)
{
cout << parr[i] << endl; // 4. 从已释放的内存读取数据 → 危险!
}
}
关键问题 :在第 2 步执行 delete[] 之后,parr 所指向的内存已经被释放,不再属于当前程序。此时 parr 变成了一个悬垂指针(dangling pointer),指向的内存随时可能被其他数据覆盖,甚至导致程序崩溃。
3. 运行后果(未定义行为)
C++ 标准规定:使用已释放的内存是未定义行为(Undefined Behavior)。这意味着:
-
程序可能看似正常运行,输出一些奇怪的值(比如还是原来的值,或者乱码);
-
程序可能立即崩溃(访问非法内存);
-
程序可能在将来某个时刻莫名其妙地崩溃,极难调试。
实际上,很多编译器或运行环境在释放内存后,可能会保留原有数据一段时间,所以"侥幸"能输出正确结果,但这绝不可依赖!一旦程序规模变大或运行环境变化,就会暴露问题。
4. 正确做法
动态分配的内存,一定要在使用完之后才释放,并且释放后不要再通过原指针访问。
cpp
void test03()
{
int* parr = func(); // 分配
for (int i = 0; i < 10; i++) // 正常使用
parr[i] = i + 1000;
for (int i = 0; i < 10; i++)
cout << parr[i] << endl;
delete[] parr; // 使用完毕,释放
}
另外,释放后最好将指针置为 nullptr,避免误用:
cpp
delete[] parr;
parr = nullptr;
5. 总结
永远不要在释放内存后继续通过原指针访问数据。写代码时请反复确认指针的生命周期,确保"分配 -- 使用 -- 释放"的顺序正确无误。
cpp
#include <iostream>
using namespace std;
int* func()
{
int* arr = new int[10];
return arr;
}
void test03()
{
int* parr = func();
for (int i = 0;i < 10;i++)
{
parr[i] = i + 1000;
}
for (int i = 0;i < 10;i++)
{
cout << parr[i] << endl;
}
//释放
delete[] parr;
}
int main()
{
test03();
return 0;
}

这段代码主要演示了 C++ 中动态内存分配 的基本用法,包括 new 分配数组、指针作为函数返回值以及手动释放内存(delete[])。下面是对代码的逐层讲解:
1. 包含头文件和命名空间
cpp
#include <iostream>
using namespace std;
-
#include <iostream>是为了使用输入输出流(这里用到了cout)。 -
using namespace std;让我们可以直接写cout而不用写std::cout,简化代码。
2. func() 函数------动态分配数组并返回指针
cpp
int* func()
{
int* arr = new int[10];
return arr;
}
-
new int[10]在堆(heap) 上申请了一块可以存放 10 个int的连续内存空间,并返回这块内存的首地址。 -
函数将地址保存在局部指针
arr中,然后返回这个指针。 -
注意:堆上的内存不会因为函数结束而自动释放,所以返回的指针在函数外部依然有效。
3. test03() 函数------使用并释放动态数组
cpp
void test03()
{
int* parr = func(); // 1. 接收动态数组的首地址
for (int i = 0;i < 10;i++) // 2. 给数组元素赋值
{
parr[i] = i + 1000;
}
for (int i = 0;i < 10;i++) // 3. 输出数组元素
{
cout << parr[i] << endl;
}
//释放
delete[] parr; // 4. 释放动态数组
}
-
接收指针 :
int* parr = func();拿到堆上数组的首地址。 -
使用数组 :通过指针
parr像普通数组一样使用下标访问和修改元素(parr[i]等价于*(parr + i))。 -
输出验证:循环打印每个元素的值(1000 到 1009)。
-
释放内存 :
delete[] parr;必须用[]告诉编译器释放的是整个数组,而不是单个元素。忘记释放会导致内存泄漏。
4. main() 函数入口
cpp
int main()
{
test03();
return 0;
}
- 调用
test03(),执行整个流程,然后程序结束。
关键知识点总结
-
堆内存分配 :
new分配的内存需要程序员手动管理(delete),生命周期由代码控制。 -
指针与数组:指向数组首元素的指针可以像数组名一样使用下标访问。
-
匹配释放 :
new搭配delete,new[]必须搭配delete[],否则行为未定义。 -
避免内存泄漏 :每次
new都要有对应的delete,确保资源被正确回收。