C++<x>new和delete

(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. 正确做法

使用完动态内存后:

  1. 及时释放delete)。

  2. 立即将指针置为 nullptr,避免误用。

cpp

复制代码
delete pf;
pf = nullptr;   // 置空后,再次解引用会明确崩溃(容易调试)
// 后续若访问 *pf 会因空指针而报错,而不是悄无声息地未定义
  1. 不要在释放后继续使用指针

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 搭配 deletenew[] 必须搭配 delete[],否则行为未定义。

  • 避免内存泄漏 :每次 new 都要有对应的 delete,确保资源被正确回收。

相关推荐
lxh01131 小时前
计算右侧小于当前元素的个数 题解
javascript·数据结构·算法
ouliten1 小时前
[CUTLASS笔记2]host端工具类
c++·笔记·cuda·cutlass
程序喵大人1 小时前
map的[]运算符,这个看似方便的语法,藏着怎样的魔鬼?
开发语言·c++·map·运算符
滴滴答滴答答1 小时前
机考刷题之 12 LeetCode 684 冗余的边
算法·leetcode·职场和发展
全栈开发圈1 小时前
新书速览|R语言医学数据分析与可视化
开发语言·数据分析·r语言
美式请加冰2 小时前
前缀数组的介绍和使用
数据结构·c++·算法
傻啦嘿哟2 小时前
爬虫跑了一小时还没完?换成列表推导式,我提前下班了
java·开发语言·jvm
青槿吖2 小时前
第一篇:Spring面试高频三连问:容器区别|Bean作用域|生命周期,一篇拿捏!
java·开发语言·网络·网络协议·spring·面试·rpc
IronMurphy2 小时前
【算法十九】33. 搜索旋转排序数组 74. 搜索二维矩阵
线性代数·算法·矩阵