【C++】拆分详解 - 内存管理

文章目录

  • 前言
  • 一、C/C++内存分布
  • 二、C语言中动态内存管理方式:malloc/calloc/realloc/free
  • 三、C++内存管理方式
    •   3.1 new/delete操作内置类型
    •   3.2 new和delete操作自定义类型
    •   3.3 operator new与operator delete函数
  • 四、new和delete的实现原理
    •   4.1 内置类型
    •   4.2 自定义类型
      •    4.2.1  底层原理分析
      •    4.2.2  示例(栈)
  • 五、 扩展思考
    •   5.1 delete[] 为什么不需要写释放个数?
    •   5.2 构造个数与析构个数不匹配
  • 六、malloc/free和new/delete的区别
  • 总结


前言

本文在C语言基本内存管理知识的基础上,介绍了C++的动态内存管理的不同之处,旨在帮助读者复习C,进而过渡到C++


一、C/C++内存分布

我们先来看下面的一段代码和相关问题,回顾复习一下C语言的知识

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在哪里?____

2. 填空题:
   sizeof(num1) = ____;  
   sizeof(char2) = ____;    strlen(char2) = ____;
   sizeof(pChar3) = ____;   strlen(pChar3) = ____;
   sizeof(ptr1) = ____;

3. sizeof 和 strlen 区别?

在这里插入图片描述

在这里插入图片描述

  • 易错
char2[] = "abcd" //开辟一个数组,将字符串abcd赋值拷贝进去
const char* pchar3 = “abcd” //创建一个指针指向常量字符串"abcd"
*char2 //解引用数组名,拿到的是首元素地址,不是拿到常量字符串

二、C语言中动态内存管理方式:malloc/calloc/realloc/free

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);
	// 2.这里需要free(p2)吗?不需要,理由如下
	free(p3);
}
malloc 动态开辟
calloc 动态开辟 + 初始化为0
realloc 动态调整 //原空间内存充足则原地调整
			   //不足则异地调整,另外开辟一片空间将原数据拷贝过去且自动销毁原空间

三、C++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理(分别对应malloc,free),往后我们将很少使用到C的内存管理方法。

  3.1 new/delete操作内置类型

void Test()
{
	// 动态申请一个int类型的空间
	int* ptr4 = new int;
	// 动态申请一个int类型的空间并初始化为10
	int* ptr5 = new int(10);
	// 动态申请3个int类型的空间
	int* ptr6 = new int[3];
	delete ptr4;
	delete ptr5;
	delete[] ptr6;
}

在这里插入图片描述

申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。(思考:如果不匹配会发生什么?后续在 5.2 处我们进行解答)

  3.2 new和delete操作自定义类型

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 和 malloc/free最大区别是 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不会。

  3.3 operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数(C++),new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

  • 【operator new 就是对 malloc 的封装,相当于 malloc + 报错功能(抛异常)】
  • 【operator delete 就是对 free 的封装】

下面我们来粗略看一下VS中的汇编代码

 /*
 operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	void* p;
	while ((p = malloc(size)) == 0) //调用malloc
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}

/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK);  
	__TRY
		pHead = pHdr(pUserData);
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
	_free_dbg(pUserData, pHead->nBlockUse); //调用_free_dbg ,实际就是free的底层
	__FINALLY
		_munlock(_HEAP_LOCK);
	__END_TRY_FINALLY
		return;
}

/*
free的实现
*/
#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)

四、new和delete的实现原理

  4.1 内置类型

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

  4.2 自定义类型

   4.2.1  底层原理分析

  • new的原理(operator new + 构造函数)
  1. 调用operator new函数申请空间
  2. 在申请的空间上执行构造函数,完成对象的构造
  • delete的原理(operator delete + 析构函数)
  1. 在空间上执行析构函数,完成对象中资源的清理工作
  2. 调用operator delete函数释放对象的空间

在这里插入图片描述

  • new T[N]的原理(operator new []+ 构造函数)
  1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对
    象空间的申请
  2. 在申请的空间上执行N次构造函数
  • delete[]的原理(operator delete[] + 析构函数)
  1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释
    放空间

在这里插入图片描述

   4.2.2  示例(栈)

在这里插入图片描述

五、 扩展思考

  5.1 delete[] 为什么不需要写释放个数?

new[] 开空间时会格外开辟一个对应类型大小的空间,用以存储开辟的个数(语法上delete[] 中不需要写个数,但实际上编译器要用,从多开的空间中拿)

在这里插入图片描述

  5.2 构造个数与析构个数不匹配

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	//手动实现析构,会报错
	//未实现,可能会报错(取决于编译器是否优化)
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

// 记住,一定要匹配使用,不匹配结果是不确定的
int main()
{
	int* p1 = new int[10];
	delete p1;

	A* p2 = new A[10];
	delete p2;

	return 0;
}

在这里插入图片描述
在这里插入图片描述

结论:

  • 手动实现析构函数时,会报错。原因如上一图。
  • 未手动实现时,可能不会报错。
    【隐患】析构函数可能本来需要起释放资源的作用,但用户忘记手动写了,不会报错,但会造成内存泄漏
    【原因】编译器如果进行优化,就不会报错,原因如上二图;如果编译器不进行优化,会报错。

六、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++的动态内存管理的常用知识,旨在帮助读者复习C,快速过渡到C++

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/538516.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

智能助力:大模型自动填写工单准确率达95%

基于大模型优秀的问答、总结和话术生成能力&#xff0c;主流联络中心纷纷接入大模型升级智能知识库、智能工单、智能陪练等应用。 以智能填单为例&#xff0c;借助大模型能够轻松从对话中提取出实体信息、判定对话意图、识别情绪、生成沟通摘要等。通过简单的Prompt&#xff0c…

鸿蒙TypeScript学习第13天:【元组】

1、TypeScript 元组 我们知道数组中元素的数据类型都一般是相同的&#xff08;any[] 类型的数组可以不同&#xff09;&#xff0c;如果存储的元素数据类型不同&#xff0c;则需要使用元组。参考文档&#xff1a;qr23.cn/AKFP8k 元组中允许存储不同类型的元素&#xff0c;元组…

三方库移植之NAPI开发[4]异步调用:CallbackPromise

写在开头&#xff1a; 本文在 三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI 的基础上修改hellonapi.cpp、index.ets&#xff0c;接着学习NAPI异步模型的Promise、Callback方式。本文共有三个示例&#xff0c;分别是Callback 异步接口示例、Promise 异步接口示例、规范异步…

【算法每日一练]-图论(lca) 最近公共祖先LCA,货车运输

目录 P3379&#xff1a;最近公共祖先LCA 思路&#xff1a; 货车运输 P3379&#xff1a;最近公共祖先LCA 思路&#xff1a; 首先进行的预处理&#xff0c;将所有点的深度和p数组求出来 设置&#xff1a;p[i][j]存的从i向上走2的j次方那么长的路径到的父节点 更深的点走到和…

STM32学习和实践笔记(8): 理解位带区和位带别名区

如前《STM32学习和实践笔记&#xff08;4&#xff09;: 分析和理解GPIO_InitTypeDef GPIO_InitStructure (b)&#xff08;含memory mapping图&#xff09;-CSDN博客 》中所写&#xff0c; STM32一共有4GB的地址&#xff0c;对所有的寄存器、存储器、外设等进行统一编址。 每…

EaticSearch学习

ES学习目标 1、全文检索 2、ES介绍 2.1 安装&#xff08;docker&#xff09; docker pull elasticsearch:7.14.0 docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.typesingle-node" elasticsearch:7.14.0初步检索 1、_cat GET /_cat/nodes&#xff1a;查看所…

MySQL选择普通索引还是唯一索引(2/16)

普通索引和唯一索引 基本概述 MySQL中可以创建普通索引与唯一索引&#xff0c;这两种索引的区别是&#xff1a; 普通索引&#xff08;Non-Unique Index&#xff09;&#xff0c;也称为非唯一索引&#xff0c;它允许索引中的条目具有重复的键值。普通索引的主要目的是加快查询…

HarmonyOS开发学习:【DevEco Device Tool 安装配置(问题全解)】

本文介绍如何在Windows主机上安装DevEco Device Tool工具。 坑点总结&#xff1a; 国内部分网络环境下&#xff0c;安装npm包可能会很慢或者超时&#xff0c;推荐使用国内npm源&#xff08;如淘宝源、华为源等&#xff09;&#xff1b;serialport这个npm包安装的过程中需要编…

暴力枚举法

虽然暴力枚举法有时候效率低&#xff0c;时间复杂度高&#xff0c;但是在面对小规模数据集的时候&#xff0c;暴力枚举法往往是很好的思维利器。 B: 01 串的熵&#xff08;5分&#xff09; 问题描述 #include <iostream> #include <cmath> #include <algorithm…

第二十六周代码(总结 + 查缺补漏)

蓝桥云课&#xff1a;刷题数量通过139题&#xff0c;尝试解决&#xff08;未做出&#xff09;18题。 其中蓝桥杯往年真题74题&#xff0c;尝试解决&#xff08;未做出&#xff09;6题算法模板题5题经典算法题20题&#xff0c;尝试解决&#xff08;未做出&#xff09;1题算法赛…

从零自制docker-9-【管道实现run进程和init进程传参】

文章目录 命令行中输入参数长度过长匿名管道从父进程到子进程传参[]*os.File{}os.NewFile和io.ReadAllexe.LookPathsyscall.Execstrings.Split(msgStr, " ")/bin/ls: cannot access : No such file or directory代码 命令行中输入参数长度过长 用户输入参数过长或包…

红黑瓷砖(BFS和DFS)

9 6 ....#. .....# ...... ...... ...... ...... ...... #...# .#..#.45BFS import java.util.Deque; import java.util.LinkedList; import java.util.Scanner;public class Main {//. 黑色//# 红色// 黑色开始static final int N 11;static int n,m,ans 1; static char[][…

传统文字检测方法+代码实现

文章目录 前言传统文字检测方法1、基于最大稳定极值区域&#xff08;MSER&#xff09;的文字检测1.1 MSER&#xff08;MSER-Maximally Stable Extremal Regions&#xff09;基本原理代码实现——使用Opencv中的cv2.MSER_create()接口 2、基于笔画宽度变换&#xff08;Stroke Wi…

【示例】MySQL-SQL语句优化

前言 本文主要讲述不同SQL语句的优化策略。 SQL | DML语句 insert语句 插入数据的时候&#xff0c;改为批量插入 插入数据的时候&#xff0c;按照主键顺序插入 大批量插入数据的时候&#xff08;百万&#xff09;&#xff0c;用load指令&#xff0c;从本地文件载入&#x…

基于深度学习的人脸表情识别系统(PyQT+代码+训练数据集)

基于深度学习的人脸表情识别系统&#xff08;PyQT代码训练数据集&#xff09; 前言一、数据集1.1 数据集介绍1.2 数据预处理 二、模型搭建三、训练与测试3.1 模型训练3.2 模型测试 四、PyQt界面实现 前言 本项目是基于mini_Xception深度学习网络模型的人脸表情识别系统&#x…

关于nvm node.js的按照

说明&#xff1a;部分但不全面的记录 因为过程中没有截图&#xff0c;仅用于自己的学习与总结 过程中借鉴的优秀博客 可以参考 1,npm install 或者npm init vuelatest报错 2&#xff0c;了解后 发现是nvm使用的版本较低&#xff0c;于是涉及nvm卸载 重新下载最新版本的nvm 2…

4月12日重新安排行程

332.重新安排行程 332. 重新安排行程 - 力扣&#xff08;LeetCode&#xff09; 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机…

Linux网络 基础概念

目录 背景知识 互联网的发展 局域网和广域网 网络拓扑 网络协议栈 协议的概念 网络协议的分层 网络与操作系统的联系 网络传输的基本流程 IP地址和MAC地址 以太网通信 数据包的封装和分用 跨网段传输 背景知识 互联网的发展 计算机网络是计算机技术和通信技术相…

循环新蓝海,“新”从“旧”中来

浙江安吉&#xff0c;是“两山”理念——“绿水青山就是金山银山”的发源地&#xff0c;也是众多循环经济和绿色产业的根据地。这里汇集了大批已上市和待上市的相关公司的总部&#xff0c;年初刚递表港交所的闪回科技&#xff0c;就是其中之一。 主营二手手机回收和销售的闪回…

卫星图像10个开源数据集资源汇总

文章目录 1、UC Merced Land-Use 2、Indian Pines 3、KSC 4、Washington DC 5、BigEarthNet 6、水体卫星图像的图像 7、城市航拍图像分割数据集 8、游泳池和汽车卫星图像检测 9、人工月球景观数据集 10、马萨诸塞州道路数据集 1、UC Merced Land-Use 数据集下载地址&am…