C++内存管理篇

news2025/6/16 13:24:43

文章目录

  • 1. C/C++内存分布
  • 2. C++中的内存管理方式
  • 3. operator new和operator delete函数
  • 4. new和delete的实现原理
  • 5. 定位new表达式(placement-new)

1. C/C++内存分布


C语言中,为了方便管理内存空间,将内存分成了不同的区域,每个区域管理不同的数据

C++中,保持着跟C语言一样的内存区域划分

在这里插入图片描述

int main() 
{
	int a = 0; 
	const int b = 1; 
    //很多人有个误区,认为被const修饰的变量在常量区 
    //实际上,被const修饰的变量叫做常变量,它本质还是个变量,只不过有了常属性 
    //因此b是在栈区上的 
	cout << &a << endl; 
	cout << &b << endl; 

	char arr1[] = "abcd"; 
	const char* arr2 = "abcd"; 

	arr1[0]++; 
	//arr2[0]++; 
	((char*)arr2)[0]++; 
    //即使将arr2强转成char*,程序在运行的过程中也会崩溃,暴力修改常量区的数据 

	return 0; 
}

2. C++中的内存管理方式


C++语言中,我们用malloc/calloc/realloc/free函数进行内存的开辟和释放,对内存进行管理

C++中,这些函数依然可用,但在使用上是比较麻烦的;因此C++中增加了new和delete操作符,使用这两个操作符进行内存的管理

  • C语言使用的是函数进行内存管理
  • C++中new和delete是操作符,不是函数
int main()
{
	//申请一个int类型的空间
	int* p1 = new int;

	//申请一个int类型的空间并初始化
	int* p2 = new int(1);

	//申请一个数组
	int* p3 = new int[10];

	//C++11支持
	//申请一个数据并初始化
	int* p4 = new int[10]{ 1,2,3 };

	delete p1;
	delete p2;
	delete[] p3;
	delete[] p4;

	return 0;
}

相比于C语言中利用函数管理内存,C++中的管理内存方式有哪些好处:

  1. 用法更加简洁

  2. 可以控制初始化

  3. 对于自定义类型,开辟空间+自动调用对应的构造函数

    struct ListNode
    {
    public:
    	ListNode(int val)
    		:_val(val)
    		, _next(nullptr)
    	{}
    
    	int _val;
    	ListNode* _next;
    };
    
    int main()
    {
    	ListNode* node1 = new ListNode(1);
    	ListNode* node2 = new ListNode(2);
    	ListNode* node3 = new ListNode(3);
    
    	delete node1;
    	delete node2;
    	delete node3;
    
    	return 0;
    }
    
  4. new失败了以后抛异常,不需要手动检查

    void Func2()
    {
    	int n = 1;
    	while (1)
    	{
    		int* p = new int[1024 * 1024 * 10];
    		cout << n << "->" << p << endl;
    		n++;
    	}
    }
    
    int main()
    {
    	try
    	{
    		Func2();
    	}
    	catch (const exception& e)
    	{
    		cout << e.what() << endl;
    	}
    	return 0;
    }
    
  5. 对于自定义类型,申请空间时,new会调用对应的构造函数;delete会调用对应的析构函数

    class A
    {
    public:
    	A(int a = 0)
    		:_a(a)
    	{
    		cout << "A(int a)" << endl;
    	}
       
    	~A()
    	{
    		cout << "~A()" << endl;
    	}
       
    private:
    	int _a;
    };
    
    
int main()
{
   	A* p1 = new A(1);//调用一次构造
   	delete p1;//调用一次析构
    
	A* p2 = new A[10];//调用十次构造
	delete[] p2;//调用十次析构
    
	return 0;
}

3. operator new和operator delete函数


看到operator+操作符,第一直觉会以为这是对new和delete操作符的重载函数,但其实它们不是重载函数

实际上,这两个函数是系统提供的全局函数

在底层,new通过调用operator new函数来实现申请空间;delete通过调用operator delete函数来时间空间的释放

operator new函数最终通过malloc函数申请空间,申请成功则返回,否则抛异常

operator delete函数最终通过free函数释放空间

可以看到,new和delete的最底层还是使用的malloc和free函数,只不过是对它进行了一系列封装

4. new和delete的实现原理


在这里插入图片描述

对于内置类型:

  • 两者的效果差不多,不同的是new在申请空间失败时抛异常,malloc则返回NULL

对于自定义类型:

  • new会调用operator new函数申请空间,再在申请的空间上调用构造函数
  • delete会调用析构函数完成对象中的资源清理,再调用operator delete释放对象的空间
  • new T[n]会调用operator new[]函数,operator new[]函数会调用operator new函数申请n个对象的空间
  • delete[]会调用n次析构函数,完成n个对象中资源的清理,再调用operator delete[]函数,operator delete[]安徽念书会调用operator delete函数,释放n个对象的空间
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a;
};

int main()
{
	A* p3 = new A[10];
	delete[] p3;

	return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

当然,不是所有情况都会留出一块空间存放对象个数

内置类型不需要析构,也就不会留出空间

没有显示定义析构函数的自定义类型也不会留出空间,因为没有显示定义析构函数,就认为不需要析构函数释放对象中的资源

new和delete使用时一定要对应,否则可能会出现不确定的结果

int main()
{
	// 程序能运行,但不推荐
	int* p1 = new int(1);
	delete[] p1;//error 正确的写法:delete p1

	// 若A显示定义析构函数,程序陷入无限调用析构的死循环中
	// 若A没有显示定义析构函数,程序能运行,但不推荐
	A* p2 = new A(1);
	delete[] p2;//error 正确的写法:delete p2

	// 程序能运行,但不推荐
	int* p3 = new int[10];
	delete p3;//error 正确的写法:delete[] p3

	// 若A显示定义析构函数,程序报错,原因是释放的位置错了
	// 若A没有显示定义析构函数,程序能运行,但不推荐
	A* p4 = new A[10];
	delete p4;//error 正确的写法:delete[] p4

	return 0;
}
// C语言创建不带哨兵位的链表
typedef int DataType;

struct ListNode
{
	DataType val;
	struct ListNode* next;
};

struct ListNode* CreateNewNode(DataType val)
{
	struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
	if (newNode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newNode->next = NULL;
	newNode->val = val;
	return newNode;
}

struct ListNode* CreateList(int n)
{
	struct ListNode* head = CreateNewNode(-1);
	struct ListNode* tail = head;

	for (int i = 1; i <= n; i++)
	{
		struct ListNode* temp = CreateNewNode(i);
		tail->next = temp;
		tail = tail->next;
	}

	struct ListNode* cur = head->next;
	free(head);
	return cur;
}

int main()
{
	struct ListNode* head = CreateList(5);
	return 0;
}

struct ListNode
{
public:
	ListNode(int val)
		:_val(val)
		,_next(nullptr)
	{}

	int _val;
	ListNode* _next;
};

// C++创建不带哨兵位的链表
ListNode* CreateList(int n)
{
	ListNode head(-1);
	ListNode* tail = &head;
	int val = 0;
	printf("请依次输入值:>");
	for (int i = 1; i <= n; i++)
	{
		cin >> val;
		ListNode* newNode = new ListNode(val);
		tail->_next = newNode;
		tail = tail->_next;
	}
	return head._next;
}

int main()
{
	ListNode* list = CreateList(5);
	return 0;
}

5. 定位new表达式(placement-new)


在某些场景下,我们向内存申请的空间没有初始化,比如向内存池申请的空间,如果是自定义类型的对象,我们可以使用new的定义表达式进行显示调用构造函数进行初始化

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a;
};

int main()
{
	A* p1 = (A*)operator new(sizeof(A));
	new(p1)A(1);

	p1->~A();
	operator delete(p1);

	return 0;
}

malloc/free和new/delete的区别:

  • 共同点:都是从堆上申请空间,需要自己手动释放空间

  • 不同点:

    用法上:

    1. malloc/free是函数;new/delete是操作符
    2. malloc申请的空间不会初始化;new会初始化
    3. malloc申请空间时,需要手动计算空间大小;new只需加上空间类型即可,想创建多个对象,只需在[]里加上对象的个数
    4. malloc的返回值为void*,需要做强转处理;new的返回值就是空间类型,不需要处理
    5. malloc申请空间失败时返回NULL,因此使用时必须先判空;new不需要,但new需要捕获异常

    底层特性上:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

未来已来!AI大模型引领科技革命

未来已来&#xff01;AI大模型正以惊人的速度引领着科技革命。随着科技的发展&#xff0c;人工智能在各个领域展现出了非凡的能力和潜力&#xff0c;大模型更是成为了科技领域的明星。从自然语言处理到图像识别&#xff0c;从智能推荐到语音识别&#xff0c;大模型的应用正在改…

计算机找不到api-ms-win-core-path-l1-1-0的5种解决方法

在计算机使用过程中&#xff0c;我们可能会遇到各种问题&#xff0c;其中之一就是找不到某些系统文件。最近&#xff0c;许多用户反映他们在使用电脑时遇到了“找不到api-ms-win-core-path-l1-1-0文件”的问题。这个问题通常出现在Windows操作系统中&#xff0c;可能会影响到一…

《vtk9 book》 官方web版 第3章 - 计算机图形基础 (3 / 6)

3.8 演员几何 我们已经看到了光照属性如何控制演员的外观&#xff0c;以及相机如何结合变换矩阵将演员投影到图像平面上。剩下的是定义演员的几何形状&#xff0c;以及如何将其定位在世界坐标系中。 建模 计算机图形学研究中的一个重要主题是建模或表示物体的几何形状。…

HarmonyOS ArkTS工程目录结构(Stage模型)

1. ArkTS工程目录结构&#xff08;Stage模型&#xff09; 官方文档&#xff08;https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/start-with-ets-stage-0000001477980905-V2&#xff09; 1.1. AppScope AppScope > app.json5&#xff1a;应用的全局配…

高级语言讲义2010计专(仅高级语言部分)

1.编写一程序&#xff0c;对输入的正整数&#xff0c;求他的约数和。 如&#xff1a;18的约数和为1236939 #include <stdio.h>int getsum(int n){int i,sum0;for(i1;i<n;i)if(n%i0)sumi;return sum; } int main(){int sum getsum(18);printf("%d",sum); …

【UE5】游戏框架GamePlay

项目资源文末百度网盘自取 游戏框架 游戏 由 游戏模式(GameMode) 和 游戏状态(GameState) 所组成 加入游戏的 人类玩家 与 玩家控制器(PlayerController) 相关联 玩家控制器允许玩家在游戏中拥有 HUD&#xff0c;这样他们就能在关卡中拥有物理代表 玩家控制器还向玩家提供 …

XSS靶场-DOM型初级关卡

一、环境 XSS靶场 二、闯关 1、第一关 先看源码 使用DOM型&#xff0c;获取h2标签&#xff0c;使用innerHTML将内容插入到h2中 我们直接插入<script>标签试一下 明显插入到h2标签中了&#xff0c;为什么不显示呢&#xff1f;看一下官方文档 尽管插入进去了&#xff0…

腾讯云轻量服务器流量用完了怎么办?停机吗?

腾讯云轻量服务器流量用完了怎么办&#xff1f;超额流量另外支付流量费&#xff0c;流量价格为0.8元/GB&#xff0c;会自动扣你的腾讯云余额&#xff0c;如果你的腾讯云账号余额不足&#xff0c;那么你的轻量应用服务器会面临停机&#xff0c;停机后外网无法访问&#xff0c;继…

Spring Boot单元测试与热部署简析

1 Spring Boot的简介 Spring Boot是一个用于构建独立的、生产级别的Spring应用程序的框架。它简化了Spring应用程序的开发过程&#xff0c;提供了自动配置和默认配置&#xff0c;使得开发者只需专注于业务逻辑的实现&#xff0c;而不用去关注繁琐的配置问题。 Spring …

【MATLAB】MATLAB转C/C++语言并部署到VS2019

首先将你要转换的代码保存 function [outputArg1, outputArg2] test(inputArg1,inputArg2)print(inputArg1inputArg2)outputArg1double(inputArg1inputArg2);outputArg2double(inputArg1*inputArg2); end在APP中找到MATLAB Coder 选择保存的文件 C/C中需要给变量定义类型&…

江科大stm32学习笔记——【5-1】EXIT外部中断

1.中断系统 中断&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后返回原来被暂停的位置继续运行。 外部中断&#xff1a;…

推理判断-聂佳-判读4-定义判断

知识点讲解 考点1 快速识别有效信息 考点2 同构选项排除 题目 考点1 快速识别有效信息 考点2 同构选项排除 总结

ubuntu debian apt deb debootstrap根文件系统制作 利用国内源加速 清华源

参考项目 https://github.com/Kron4ek/Wine-Builds/blob/master/create_ubuntu_bootstraps.sh 制作一个最小的deb 根文件系统 sudo apt install debootstrap debootstrap --arch amd64 jammy ~/tmp/ubuntu-jammy-bootstrap https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ 命…

【kubernetes】关于k8s集群中的ingress规则案例

目录 一、k8s 对外服务之 Ingress 1.1什么是ingress 1.2外部的应用能够访问集群内的服务有哪些方案&#xff1f; 1.3Ingress 组成 1.4Ingress-Nginx 工作原理 1.5ingress 暴露服务的方式 二、实操ingress暴露服务 前期.部署 nginx-ingress-controller 2.1基于host网络…

Hi3516DV500+SC2210 AIISP 黑光相机

1. Hi3516DV500 Hi3516DV500是一颗面向行业市场推出的高清智能网络摄像头SoC。该芯片最高支持2路sensor输入&#xff0c;支持最高5M30fps的ISP图像处理能力&#xff0c;支持2F WDR、多级降噪、六轴防抖、多光谱融合等多种传统图像增强和处理算法&#xff0c;支持通过AI算法对输…

实施工程师的项目流程

实施工程师负责项目的执行和实施&#xff0c;他的项目流程&#xff1a; 1. 项目准备阶段&#xff1a; - 确认项目需求和目标。 - 制定项目计划和工作范围。 - 分配资源和制定预算。 - 确定项目团队成员和角色。 2. 设计和准备阶段&#xff1a; - 分析和评估项…

187基于matlab的弹道目标跟踪滤波方法

基于matlab的弹道目标跟踪滤波方法&#xff0c;扩展卡尔曼滤波&#xff08;extended Kalman filter, EKF&#xff09;、转换测量卡尔曼滤波&#xff08;conversion measurement Kalman filter, CMKF&#xff09;跟踪滤波&#xff0c;得到距离、方位角、俯仰角误差结果。程序已调…

力扣hot100:76.最小覆盖子串(滑动窗口)

本题使用滑动窗口解决&#xff0c;用right表示滑动窗口的右边界&#xff0c;left表示滑动窗口的左边界。寻找可行解&#xff0c;我们可以这样约定滑动窗口的意义&#xff1a;right指针向右移动&#xff0c;是使得滑动窗口找到可行解。left指针向右移动是为了更新窗口使得其可以…

【蓝牙协议栈】【经典蓝牙】【BLE蓝牙】蓝牙协议规范(射频、基带链路控制、链路管理)

目录 1. 蓝牙协议规范&#xff08;射频、基带链路控制、链路管理&#xff09; 1.1 射频协议 1.2 基带与链路控制协议 1.3 链路管理器 1. 蓝牙协议规范&#xff08;射频、基带链路控制、链路管理&#xff09; 蓝牙协议是蓝牙设备间交换信息所应该遵守的规则。与开放系…

VMware虚拟机安装Ubuntu kylin22.04系统教程(附截图详细步骤)

一、版本信息 虚拟机产品&#xff1a;VMware Workstation 17 Pro 虚拟机版本&#xff1a;17.0.0 build-20800274 ISO映像文件&#xff1a;ubuntukylin-22.04-pro-amd64.iso 二、安装步骤 打开虚拟机&#xff0c;点击创建新的虚拟机&#xff1a; 选择自定义&#xff1a; 硬…