震 惊!90% 的 程 序 员 都 不 知 道 的 new / delete 底 层 黑 幕

💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 C++。
💡个 人 主 页:[@笑口常开xpr 的 个 人 主 页]
(https://blog.csdn.net/2301_78847073?spm=1000.2115.3001.5343)
📚系 列 专 栏C++ 炼 魂 场:从 青 铜 到 王 者 的 进 阶 之 路
代 码 趣 语:new[ ] 与 delete[ ] 是 内 存 世 界 的 “连 锁 反 应”,必 须 成 对 出 现。若 误 用 单 元 素 操 作 符,将 引 发 内 存 崩 溃 的 多 米 诺 效 应。
💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。
📦gitee 链 接gitee

在这里插入图片描述

         C++ 内 存 管 理 是 从 “手 动 分 配” 到 “对 象 自 动 管 理” 的 升 级。从 C 的 malloc 到 C++ 的 new,不 仅 是 语 法 变 化,更 是 对 “对 象 生 命 周 期” 的 完 整 掌 控。本 文 拆 解 new / delete 的 底 层 逻 辑、与 构 造 / 析 构 函 数 的 协 同,以 及 内 存 泄 漏 的 规 避,帮 你 夯 实 基 础。


程 序 中 的 数 据

又 叫 堆 栈,存 储 非 静 态 局 部 变 量 / 函 数 参 数 / 返 回 值 等 等,栈 是 向 下 增 长 的。

内 存 映 射 段 是 高 效 的 I / O 映 射 方 式,用 于 装 载 一 个 共 享 的 动 态 内 存 库。用 户 可 使 用 系 统 接 口 创 建 共 享 共 享 内 存,做 进 程 间 通 信。

用 于 程 序 运 行 时 动 态 内 存 分 配,堆 是 可 以 向 上 增 长的。

数 据 段(静 态 区) 存 储 全 局 数 据 和 静 态 数 据。

代 码 段(代 码 段) 可 执 行 的 代 码 / 只 读 常 量。

程 序 是 以 文 件 形 式 存 储 在 磁 盘 中 的。
在这里插入图片描述


C 语 言 中 动 态 内 存 管 理 方 式

动 态 内 存 分 配
         C 语 言 是 通 过 mallocrealloccalloc 来 开 辟 空 间 的。
         malloc 用 来 在 内 存 的 堆 区 分 配 一 块 连 续 空 间,但 不 会 对 分 配 的 内 存 进 行 初 始 化。
         calloc 用 来 分 配 指 定 类 型 和 大 小 的 空 间 并 且 会 把 分 配 的 内 存 全 部 初 始 化 为 0。
         realloc 会 将 已 经 开 辟 的 空 间 大 小 调 整,也 就 是 扩 容。


C++ 内 存 管 理 方 式

         C++ 兼 容 C 语 言,C 语 言 的 内 存 管 理 方 式 在 C++ 中 同 样 适 用。在 C++ 中 使 用 new 和 delete 操 作 符 来 管 理 内 存。


new 和 delete

new 运 算 符:分 配 和 初 始 化 内 存
delete 运 算 符:释 放 内 存


基 本 语 法

//分配单个对象
T* ptr = new T;        //默认初始化(内置类型未初始化,类类型调用默认构造函数)
delete ptr;
T* ptr = new T(value); //值初始化(内置类型初始化为指定值,类类型调用带参构造函数)
delete ptr;

//分配数组
T* arr = new T[n];     //分配包含n个元素的数组,默认初始化
delete[ ] arr;
T* arr = new T[n]();   //值初始化(C++11及以后)
delete[ ] arr;

在这里插入图片描述
注 意
         申 请 和 释 放 单 个 元 素 的 空 间,使 用 new 和 delete 操 作 符,申 请 和 释 放 连 续 的 空 间,使 用 new[ ] 和 delete[ ] 应 该 匹 配 起 来 使 用。


代 码 示 例

#include<iostream>
using namespace std;
void Test()
{
	//动态申请一个int类型的空间
	//C
	int* p1 = (int*)malloc(sizeof(int));
	free(p1);
	//C++
	int* p2 = new int;
	delete p2;//free(p2);

	//动态申请10个int类型的空间
	//C
	int* p3 = (int*)malloc(sizeof(int) * 10);
	free(p3);
	//C++
	int* p4 = new int[10];
	delete[] p4;

	//动态申请一个int类型的空间并初始化为10
	//C
	int* p5 = (int*)calloc(10, sizeof(int));
	free(p5);
	//C++
	int* p6= new int(10);
	delete p6;
}
int main()
{
	Test();
	return 0;
}

         相 比 于 C 语 言,C++ 内 存 分 配 语 法 更 简 洁。


构 造 函 数 调 用

         对 于 类 类 型,new 会 自 动 调 用 相 应 的 构 造 函 数。下 面 分 别 以 C 语 言 和 C++ 的 分 配 内 存 进 行 比 较。

C 语 言

#include<iostream>
using namespace std;
struct ListNode
{
	int _val;
	struct ListNode* _next;
};
struct ListNode* BuyListNode(int x)
{
	//单纯的开空间
	struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->_next = NULL;
	newnode->_val = x;
	return newnode;
}
int main()
{
	struct ListNode* n1 = BuyListNode(1);
	struct ListNode* n2 = BuyListNode(2);
	struct ListNode* n3 = BuyListNode(3);
	return 0;
}

C++

#include<iostream>
using namespace std;
class ListNode
{
public:
	//构造函数
	ListNode(int x)
	//初始化列表
		: _val(x)
		, _next(NULL)
	//函数体
	{}
private:
	int _val;
	struct ListNode* _next;
};
int main()
{
	//开空间并调用构造函数
	ListNode* nn1 = new ListNode(1);
	ListNode* nn2 = new ListNode(2);
	ListNode* nn3 = new ListNode(3);
	return 0;
}

         在 申 请 自 定 义 类 型 的 空 间 时,new 会 调 用 构 造 函 数,delete 会 调 用 析 构 函 数,而 malloc 与 free 不 会。


区 别

特性C 语言实现C++ 实现
数据封装结构体 + 外部函数类 + 成员函数
内存分配malloc()+ 手动初始化new+ 构造函数自动初始化
错误处理返回NULL+perror()抛出异常
类型转换显式转换自动类型推导
生命周期手动管理 (分配 / 释放)构造函数 / 析构函数自动管理
代码复杂度较高 (需要额外函数)较低 (封装在类中)

注 意
         malloc 和 free、new 和 delete 匹 配 使 用,不 能 乱 用 否 则 代 码 会 报 错。


operator new 与 operator delete 函 数

         new 和 delete 分 别 是 内 存 开 辟 和 释 放 的 操 作 符,operator new 与 operator delete 是 全 局 函 数,new 在 底 层 调 用 operator new 全 局 函 数 来 申 请 空 间,delete 在 底 层 通 过 operator delete 全 局 函 数 来 释 放 空 间。

operator new
在这里插入图片描述
在这里插入图片描述
operator delete
在这里插入图片描述
在这里插入图片描述

#include<iostream>
using namespace std;
int main()
{
	int* p1 = (int*)operator new(sizeof(int));
	operator delete(p1);
	int* p2 = new int;
	delete(p2);
	return 0;
}

         从 上 面 的 代 码 来 看,operator new 和 operator delete 和 malloc 以 及 free 的 用 法 类 似。面 向 对 象 语 言 处 理 失 败 后 不 使 用 返 回 值,会 抛 异 常 并 捕 获 异 常,捕 获 异 常 后 会 释 放 空 间。
malloc

#include<iostream>
using namespace std;
int main()
{
	int* p1 = nullptr;
	do
	{
		p1 = (int*)malloc(1024 * 1024);//失败了返回NULL,申请的是虚拟内存
		cout << p1 << endl;
		} while (p1);
	return 0;
}

         malloc 开 辟 空 间 失 败 后 失 败 后 程 序 会 终 止,并 返 回 空 指 针。


new

#include<iostream>
using namespace std;
int main()
{
	int* p1 = nullptr;
	try
	{
		do
		{
			p1 = new int[1024 * 1024];
			cout << p1 << endl;
		} while (p1);
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

抛 出 异 常
在这里插入图片描述
在这里插入图片描述
         new 开 辟 空 间 失 败 后 程 序 会 抛 出 异 常。使 用 new 会 调 用 operator new,operator new 对 malloc 进 行 了 封 装,如 果 开 辟 空 间 失 败 会 抛 出 bad allocation。开 辟 空 间 成 功 后 会 调 用 构 造 函 数。
         与 new 类 似,delete 是 先 调 用 析 构 函 数 清 理 资 源,然 后 释 放 空 间,释 放 空 间 时 调 用 operator delete,然 后 调 用 free 函 数。
在这里插入图片描述


#include<iostream>
using namespace std;
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		cout << "Stack(size_t capacity = 3)" << endl;
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc fail");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
void TestStack()
{
	Stack s;
	s.Push(1);
	s.Push(2);
}
int main()
{
	//需要申请1个堆上的栈对象
	Stack* p1 = new Stack;
	delete p1;
	return 0;
}

在这里插入图片描述


new 和 delete 的 实 现 原 理


内 置 类 型

         如 果 申 请 的 是 内 置 类 型 的 空 间,new 和 malloc,delete 和 free 基 本 类 似,不 同 的 地 方 是:new / delete 申 请 和
         释 放 的 是 单 个 元 素 的 空 间,new[ ] 和 delete[ ] 申 请 的 是 连 续 空 间,而 且 new 在 申 请 空 间 失 败 时 会 抛 异 常,malloc 会 返 回 NULL。


自 定 义 类 型

new 的 原 理

  1. 调 用 operator new 函 数 申 请 空 间。
  2. 在 申 请 的 空 间 上 执 行 构 造 函 数,完 成 对 象 的 构 造。

delete 的 原 理

  1. 在 空 间 上 执 行 析 构 函 数,完 成 对 象 中 资 源 的 清 理 工 作。
  2. 调 用 operator delete 函 数 释 放 对 象 的 空 间。

new T[N] 的 原 理

  1. 调 用 operator new[ ] 函 数,在 operator new[ ] 中 实 际 调 用 operator new 函 数 完 成 N 个 对 象 空 间 的 申 请
  2. 在 申 请 的 空 间 上 执 行 N 次 构 造 函 数

delete[ ] 的 原 理

  1. 在 释 放 的 对 象 空 间 上 执 行 N 次 析 构 函 数,完 成 N 个 对 象 中 资 源 的 清 理
  2. 调 用 operator delete[ ] 释 放 空 间,实 际 在 operator delete[ ] 中 调 用 operator delete 来 释 放 空 间

定 位 new(placement-new)表 达 式

定 义

         定 位 new 表 达 式 是 在 已 分 配 的 原 始 内 存 空 间 中 调 用 构 造 函 数 初 始 化 一 个 对 象。


格 式

  1. new (place_address) type
  2. new (place_address) type(initializer-list)
    place_address 必 须 是 一 个 指 针,initializer-list 是 类 型 的 初 始 化 列 表。

代 码 示 例

#include<iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = (A*)malloc(sizeof(A));
	//显示调用构造函数
	new(p1)A; //如果A类的构造函数有参数时,此处需要传参
	p1->~A();
	free(p1);
	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);
	p2->~A();
	operator delete(p2);
	return 0;
}

在这里插入图片描述
         任 何 类 型 的 指 针 都 是 内 置 类 型,内 置 类 型 不 会 自 动 调 用 构 造 函 数 和 析 构 函 数。只 有 自 定 义 类 型 才 能 自 动 调 用 构 造 函 数 和 析 构 函 数。因 为 p1 是 内 置 类 型 需 要 手 动 调 用 构 造 函 数 和 析 构 函 数。


经 典 题 目

malloc / free 和 new / delete 的 区 别

共 同 点

         malloc / free 和 new / delete 都 是 从 堆 上 申 请 空 间,并 且 需 要 用 户 手 动 释 放。


不 同 点

  1. malloc 和 free 是 函 数,new 和 delete 是 操 作 符
  2. malloc 申 请 的 空 间 不 会 初 始 化,new 可 以 初 始 化
  3. malloc 申 请 空 间 时,需 要 手 动 计 算 空 间 大 小 并 传 递,new 只 需 在 其 后 跟 上 空 间 的 类 型 即 可,如 果 是 多 个 对 象,[ ] 中 指 定 对 象 个 数 即 可
  4. malloc 的 返 回 值 为 void*,在 使 用 时 必 须 强 转,new 不 需 要,因 为 new 后 跟 的 是 空 间 的 类 型
  5. malloc 申 请 空 间 失 败 时,返 回 的 是 NULL,因 此 使 用 时 必 须 判 空,new 不 需 要,但 是 new 需 要 捕 获 异 常
  6. 申 请 自 定 义 类 型 对 象 时,malloc / free 只 会 开 辟 空 间, 不 会 调 用 构 造 函 数 与 析 构 函 数,而 new 在 申 请 空 间 后 会 调 用 构 造 函 数 完 成 对 象 的 初 始 化,delete 在 释 放 空 间 前 会 调 用 析 构 函 数 完 成 空 间 中 资 源 的 清 理。

内 存 泄 漏

定 义

         内 存 泄 漏 是 指 程 序 在 动 态 分 配 内 存 后,由 于 逻 辑 错 误 或 设 计 缺 陷,导 致 已 分 配 的 内 存 不 再 被 使 用,却 未 被 释 放,最 终 造 成 系 统 可 用 内 存 逐 渐 减 少 的 现 象。内 存 泄 漏 如 果 长 期 积 累,可 能 导 致 程 序 性 能 下 降、卡 顿,甚 至 崩 溃。


核 心 原 因

         动 态 分 配 的 内 存 与 指 向 它 的 指 针 失 去 关 联,且 无 法 被 程 序 再 次 访 问 和 释 放。


危 害

短 期 影 响:程 序 占 用 内 存 持 续 增 长,可 能 导 致 运 行 速 度 变 慢(内 存 交 换 频 繁)。
长 期 影 响:对 于 长 时 间 运 行 的 程 序,内 存 泄 漏 会 逐 渐 耗 尽 系 统 内 存,最 终 导 致 程 序 崩 溃 或 被 操 作 系 统 强 制 终 止。
隐 蔽 性:单 次 内 存 泄 漏 可 能 微 不 足 道,但 长 期 积 累 会 引 发 严 重 问 题 且 排 查 难 度 较 高。
在这里插入图片描述


总 结

         C++ 内 存 管 理 的 核 心 是 “智 能” - - - new / delete 结 合 构 造 / 析 构,实 现 对 象 从 创 建 到 销 毁 的 自 动 化。相 比 C 的 手 动 操 作,它 降 低 了 错 误 风 险,但 仍 需 注 意 匹 配 使 用、避 免 指 针 滥 用。掌 握 这 些,既 能 应 对 面 试,更 能 写 出 可 靠 代 码。

评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值