初识C++ · 内存管理

news2024/11/17 17:36:50

目录

1 C/C++的内存分布

2 C语言的内存管理

3 C++的内存管理

4 operator new 和 operator delete

5 定位new


1 C/C++的内存分布

语言不同,内存分布是相同的,对于局部变量都是放在栈上,全局变量都是放在静态区(数据段),动态开辟的都是从堆中开辟,const修饰的变量也是都放在常量区(代码段)

这里试试手:

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);
}

globalVar在哪里?staticGlobalVar在哪里?staticVar在哪里? localVar在哪里?num1 在哪里?char2在哪里? *char2在哪里?pChar3在哪里?*pChar3在哪里? ptr1在哪里? *ptr1在哪里?

A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)

globalVar是全局变量,所以在静态区,C

StaticGlobalVar是被static修饰的变量,所以在静态区,C

StaticVar也是被static修饰的,所以在静态区,C

localVar是局部变量,所以在栈,A

num1是地址,在栈上,A;char 2也是地址,在栈上,

A;*char2是常量字符串的第一个字符,所以在常量区,D

pChar3是地址,栈上,A

*pChar3同*char2,在常量区,D

ptr1是地址,栈上,A

*ptr1是动态开辟的第一个空间的元素,堆上,B


2 C语言的内存管理

C语言中的内存是用函数的形式进行管理的,涉及的函数是malloc realloc calloc free,均被包含在头文件stdlib里面,malloc函数和calloc函数的最大区别就是是否初始化的问题,realloc是用来扩容,使用完会自动是空间,free是释放空间。

似乎看起来比较完美了,但是为什么C++有单独的内存管理方式呢?

这是因为:

int main()
{
	int* pa = (int*)malloc(sizeof(int) * 10);
	free(pa);
	return 0;
}

malloc等函数只能用来申请内置类型的空间,对于自定义类型就没有办法,顺便提一句,涉及内存管理的时候,最好注意有没有内存泄露的问题,内存泄露是慢性病,很严重的喔。

那么,C++就引入了不同的方式进行内存管理,其实C语言的内存管理的方式在C++里面也是可以使用的,但是局限性比较大,当我们学会后面的内存管理方式之后,就把malloc函数等放在一边吧


3 C++的内存管理

在C++中内存管理是使用new 和 delete来实现的,这两个都是C++中的标识符,和C语言不同的是C++实现内存管理不是用的函数。

首先,对于内置类型来说:

int* p1 = new int;

这写法等价于:

int* p1 = (int*)malloc(sizeof(int) * 1);

一下就简洁了,与calloc不同的是,这样不能进行初始化,那么要进行初始化可以在后面加括号:

int* p1 = new int(1);

通过解引用打印出来也是1,与calloc不同的是初始化可以任意初始化,calloc只能初始化为0。

那么开辟一个数组使用到的是方括号:

int* pa = new int[10];

这种写法是开辟了一个大小为40字节的数组,初始化的方式是使用花括号:

int* pa = new int[10]{1,2,3,4};

这里和数组是一样,如果不是初始完全的话,那么剩下的部分就会自动初始化为0。

既然是开辟空间,方括号里面的也可以是变量,同malloc函数一样,可以使用变量:

int n = 10;
int* pa = new int[n]{1,2,3};

当然,C++引入了new和delete可不是为了内置类型的,是为的自定义类型:
这里使用类A介绍:

class A
{
public:
	A(int n)
		:_a(n)
		,_b(n - 1)
	{
		cout << "int n" << endl;
	}
	A(int a, int b)
		:_a(a)
		,_b(b)
	{
		cout << "int a, int b" << endl;
	}
	~A()
	{
		_a = _b = -1;
		cout << "~A" << endl;
	}
private:
	int _a;
	int _b;
};

3.1 new

使用:

A* pA = new A;//开辟一个A
A* pB = new A(0);//对pB对象初始化
A* pC = new A[10];//开辟10个对象的空间
A* pD = new A[10]{1,2,3,{1,3}};//初始化对象

对于自定义类型来说,需要注意的是初始化其实使用了隐式类型转换,对于多参数的对象来说,就是花括号里面套一个花括号就可以了。

这里可能有个问题就是A* pB = new A(0)给的是匿名对象吗?并不是,因为匿名对象的生命周期只有这一行,后面就直接析构了,但是这里没有,所以不是匿名对象。

这是开辟空间的用法,你如果认为只有这么简单就错辣!

new比malloc高级在于,new不仅仅包含了malloc,我们逐一介绍,这里不卖关子,直说了就:
new = malloc + 抛异常 + 构造函数,其中malloc + 抛异常 = operator new,大概是可以这样理解的。

首先,new一个自定义类型的时候是会自动调用对应的构造函数的:

new一个的时候如果内存足够,就会调用对应的构造函数,光是能调用对应的构造函数,就可以节省很多事:

struct ListNode
{
	//struct ListNode* _next;
    ListNode* _next;
	int _val;
	ListNode(int val)
		:_next(nullptr)
		,_val(val)
	{}
};
void Func()
{
	ListNode* n1 = new ListNode(1);
	ListNode* n2 = new ListNode(2);
	ListNode* n3 = new ListNode(3);
	n1->_next = n2;
	n2->_next = n3;
	delete n1;
	delete n2;
	delete n3;
}

既然能调用构造函数,手搓链表的时候只能说太爽了,至于struct写不写看文件后缀,C++中里面的那个struct可以不用写。

int main()
{
    A* pa = new A(0);
    return 0;
}

我们再来看看这段代码的反汇编:

F11调试之后,跳转了几次来到了这里,就会发现,new的底层调用的其实是malloc,所以new是对malloc的一个封装,跳转之前我们还可以看到:

有operator new和A::A,先进入了operator new就会发现里面有我们熟悉的malloc,后面再调用了构造函数。

为什么说new用来手搓链表就很爽,因为我们不用没开辟一个就去assert一下,new里面的抛异常会帮我们解决;

因为抛异常是后面的知识了,这里简单介绍,在C语言中报错是通过返回错误码,比较暴力,在C++中有一种温和的报错方式就是抛异常,通过try - catch ,比如我一次性开辟很大的空间,系统分配的内存不够了,那么try-catch就会起作用,程序运行完毕会打印一行字,告诉你有错误,new里面的抛异常就是为了防止开辟空间不够,因为链表每次开辟一个节点就要检查是否为空,new底层中的malloc开辟之后,抛异常直接就帮忙解决了空指针的问题(因为malloc函数开辟失败返回的是空指针)。

总结来说就是:new = malloc + 抛异常 + 构造函数,new是对malloc的封装,对内置类型来说new和malloc没有区别,对自定义类型来说new不仅可以开辟空间,还可以抛异常并且调用对应的构造函数。

3.2 delete

同C语言,开辟了空间,使用完就要还给操作系统,C语言中使用的是free,在C++中使用的是delete,delete = operator delete+ 析构,而operator delete最终是通过free实现的:

这里当我们进入到operator delete的汇编就可以发现:

最后调用的其实就是free,这里是free的宏,即operator delete最终使用free释放。

delete的大致使用如下:

int main()
{
	A* pa = new A(0);
	A* pb = new A[10]{1,2,3,4};
	delete pa;
	delete[] pb;
	return 0;
}

对于pb来说,我们开辟了10个A类型的空间,也就是我们开辟了80个字节,delete就会释放这么多字节,但是!

当我们显式调用了析构函数之后,真的只开辟了80个字节吗?

在2019中,我们进入到operator new中就可以看到:

我们明明只要40个字节,却多开了4个字节,但是当我们注释掉析构函数之后,我们再去调试,就会发现size变成了40,也就是有没有显式调用析构函数会影响实际开辟的空间大小,这里我们再引入一个点:

C语言和C++中的内存管理混用可以吗?

用malloc函数开辟的我用delete,用new的我用free,你说可行吗?实际上对于内置类型来说是没有问题的,因为不会涉及多开空间的问题,也就不会涉及越界的问题,那么对于自定义类型来说:

当我们显式调用了析构函数,使用free就会出问题,其实不管是free还是delete都会出问题:

其中的缘由就是多开的4个空间是用来干嘛的,当显式调用析构函数之后,多开辟的空间就是用来记录有多少个空间需要被析构,是一个数字用来记录,那么就多开了一个整型,所以会多4字节,如果没有显式调用析构就不会,显式调用就相当于告诉编译器我有这么多空间需要销毁,你记得数数。

那么为什么会出现越界的问题?

因为开辟了多少空间,delete[] 就知道要销毁这么多空间,在delete[]对应的汇编中有一条语句是回退4个字节,所以delete[]操作的时候就不会出现越界的问题,对于free delete来说没有回退的指令就会出现越界的问题,这也就是为什么不要混用的原因,挺容易出现幺蛾子的。

delete是operator delete + 析构,那么有没有想过,析构的是哪里?operator delete的free是free的哪里呢?

这个问题是比较简单的,不卖管子了就,free是free掉指针指向的区域,析构是完成资源清理工作(如果有资源需要清理的话),有点像两个栈实现队列的那种说法。


4 operator new 和 operator delete

其实在上面new和delete的介绍已经把这两个介绍的差不多了,这里补充一些点即可。

operator new 和 operator delete是全局函数,不是C++中的标识符,我们通过它们的定义就可以知道 operator new 是调用的malloc实现的开辟空间,operator delete是调用的free来释放的空间,

既然是全局函数,意味我们可以显式的调用:

显式调用的情况,operator new和malloc函数的用法是一样的,都不能进行初始化,operator delete使用和free是一样的:

int main()
{
	int* p1 = (int*)operator new(sizeof(int) * 10);
	A* pa = (A*)operator new (sizeof(A) * 10);
	operator delete(p1);
	operator delete(pa);
	return 0;
}

它们主要的内容还是前面讲的。


5 定位new

定位new这里了解一下就行了:

使用格式: new (place_address) type或者new (place_address) type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表

int main()
{
	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A;
	A* p3 = (A*)operator new(sizeof(A));
	new(p3)A(1);
	return 0;
}

我们平常显式调用析构函数是没有问题,但是如果想显式调用构造函数就有问题了,构造函数是初始化用的,也就是一旦对象实例化了,就会自动的调用对应的构造函数,像上面的情景,指针指向一块空间,我们相对它进行初始化不能直接显式的调用构造函数,这里就需要用定位new了,格式如上,如果是多个对象,调用构造可以这样:

int main()
{
	A* p4 = (A*)operator new(sizeof(A) * 10);
	for (int i = 0; i < 10; i++)
	{
		new(p4 + i)A{ i };
	}
	A* p5 = (A*)operator new(sizeof(A) * 10);
	new(p5)A[5]{ 1,2,3,4,5 };
	return 0;
}

有两种初始化方法,比较推荐的是for循环,因为好控制,一旦空间大了,比如1000个,一个一个写显然是不可能的,所以一般用for循环。

定位new一般是用于内存池的情况,因为从内存池分配出来的空间都是没有初始化的,所以需要用到定位new,但是更多的细节放在后面介绍,是计算机网络的知识了。


感谢阅读!

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

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

相关文章

O2OA(翱途)支持高斯_openGauss,瀚高_HighGo,磐维_panweidb等各种国产postgres分支数据库接入

O2OA&#xff08;翱途&#xff09;作为一款企业级应用平台&#xff0c;其支持多种数据库系统是其灵活性和可扩展性的重要体现。从MySQL、Oracle到国产的达梦、神州等数据库&#xff0c;再到对PostgreSQL的原生支持&#xff0c;O2OA展现了其对不同数据库环境的良好适应性。特别地…

Vue-路由介绍

目录 一、思考引入 二、路由介绍 一、思考引入 单页面应用程序&#xff0c;之所以开发效率高&#xff0c;性能高&#xff0c;用户体验好&#xff0c;是因为页面按需更新。 而如果要按需更新&#xff0c;首先需要明确&#xff1a;访问路径和组件的对应关系。该关系通过路由来…

2024最新同城吃喝玩乐小程序源码+同城分类信息小程序搭建+开源无需授权+详细图文安装部署教程

在繁忙的都市生活中&#xff0c;生活节奏飞快&#xff0c;人们希望能够快速、便捷地获取各类生活信息&#xff0c;满足日常的吃喝玩乐需求。同城吃喝玩乐同城分类信息小程序&#xff0c;非常受欢迎&#xff0c;提供一站式便捷生活新体验。 分享一款2024最新同城吃喝玩乐小程序…

1707jsp电影视频网站系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 校园商城派送系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用web模式&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数…

基于springboot+mybatis+vue的项目实战之前端

步骤&#xff1a; 1、项目准备&#xff1a;新建项目&#xff0c;并删除自带demo程序&#xff0c;修改application.properties. 2、使用Apifox准备好json数据的mock地址 3、编写基于vue的静态页面 4、运行 整个的目录结构如下&#xff1a; 0、项目准备 新建项目&#xff0…

【可实战】被测需求理解(需求文档是啥样的、从哪些角度进行需求评审、需求分析需要分析出哪些内容、如何提高需求分析能力)

产品人员会产出一个需求文档&#xff0c;然后组织一个需求的宣讲。测试人员的任务就是在需求宣讲当中&#xff0c;分析需求有没有存在一些问题&#xff0c;然后在需求宣讲结束之后通过分析需求文档&#xff0c;分析里面的测试点并预估一个排期。 一、需求文档是什么样的&#x…

西红柿叶病害检测(yolov8模型,从图像、视频和摄像头三种路径识别检测,包含登陆页面、注册页面和检测页面)

1.基于最新的YOLOv8训练的西红柿病害检测模型&#xff0c;和基于PyQt5制作的可视西红柿病害系统&#xff0c;包含登陆页面、注册页面和检测页面&#xff0c;该系统可自动检Bacterial Spot, Early_Blight, Healthy, Late_blight, Leaf Mold, Target_Spot, black spot&#xff0c…

同步时序电路的分析方法

同步时序电路的分析方法 基本步骤 Step1&#xff1a;写方程式 时钟方程 &#xff1a;各个触发器时钟信号的逻辑表达式&#xff0c;同步时序电路可省去不写 输出方程&#xff1a;时序电路的输出逻辑表达式&#xff0c;通常为现态和输入变量的函数 驱动方程 &#xff1a;各触发器…

【Java】初识网络编程

文章目录 前言✍一、互联网的发展1.独立模式2.网络的出现局域网LAN广域网WAN ✍二、网络编程概述✍三、网络编程中的术语介绍IP地址端口号协议OSI七层模型TCP\IP四层模型 ✍四、协议的层级之间是如何配合工作的 前言 在本文中&#xff0c;会对网络编程的一些术语进行解释&#…

【npm】解决npm包突然消失MODULE_NOT_FOUND

今天折腾新特性时需要升级nodejs&#xff0c;安装新版后npm离奇消失了。C:\Users\**用户名\AppData\Roaming\npm\node_modules下只有cnpm&#xff0c;没有npm的目录。重装nodejs也不好使。 机智如我&#xff0c;试了下cnpm -v是正常的&#xff0c;而且能看到nodejs&#xff0c;…

start.spring.io不支持java8,idea使用阿里云又报错

做项目的时候&#xff0c;我们可以发现&#xff0c;访问https://start.spring.io/ 创建脚手架项目的时候&#xff0c;最低是java 17了 但是对于很多项目来说&#xff0c;还是在用java8&#xff0c;这该怎么办呢&#xff1f; 值得庆幸的是&#xff0c;阿里云也同样有相同功能的…

掌握MySQL常用的命令

前言 MySQL是一个流行的开源关系型数据库管理系统&#xff0c;广泛应用于各种应用场景。熟练掌握MySQL的常用命令&#xff0c;对于数据库管理员和开发人员来说至关重要。本文将介绍MySQL数据库的一些基础术语、SQL语言分类&#xff0c;以及DDL、DML、DQL和DCL等操作&#xff0…

浅了解UE5

1.什么是UE5 UE5&#xff08;Unreal Engine 5&#xff09;是一款由Epic Games开的游戏引擎&#xff0c;它是UE4的后续版本。UE5于2021年5月首次发布&#xff0c;并在2022年正式发布。UE5引入了许多令人兴奋的新功能和改进&#xff0c;以下是一些主要的介绍&#xff1a; 1. 光线…

编程入门(六)【Linux系统基础操作四】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 &#x1f525;前言&#x1f680;if else条件控制基本的if语句带else的if语句嵌…

通俗易懂讲解 nginx-rtmp-module 是干嘛的?

文章目录 概述安装 nginx 和 nginx-rtmp-module配置 Nginx启动 Nginx使用示例推流拉流转码鉴权与安全自动录制 概述 nginx-rtmp-module 是一个用于 Nginx 的第三方模块&#xff0c;它扩展了 Nginx 服务器的功能&#xff0c;使其能够处理实时流媒体数据&#xff0c;特别是支持 …

leetcode刷题(5): STL的使用

文章目录 56. 合并区间解题思路c实现 55. 跳跃游戏解题思路c 实现 75. 颜色分类解题思路c 实现 36 下一个排列解题思路c 实现 56. 合并区间 题目: 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&a…

仓库管理系统需求调研要点

仓库管理系统需求调研 一、仓库的作用 仓库分类 原材料仓库&#xff1a;用于存放生产所需的原材料和零部件&#xff0c;需要保持原材料的质量和数量稳定。半成品仓库&#xff1a;存放生产过程中的半成品和在制品&#xff0c;需要保持良好的生产流程和及时出库。成品仓库&#x…

tabby多个窗口同时执行插件

一、插件名称 安装插件quick-cmds 二、使用 点击右上角图标&#xff0c;选中命令单机即可。快捷键ALTQ唤出列表&#xff0c;不用每次用鼠标点击右上角&#xff0c;巴适的板。 ctrl enter发送全部

EditReady for Mac激活版:专业视频转码工具

对于视频专业人员来说&#xff0c;一款高效的视频转码工具是不可或缺的。EditReady for Mac正是这样一款强大的工具&#xff0c;它拥有简洁直观的操作界面和强大的功能&#xff0c;让您的视频处理工作事半功倍。 EditReady for Mac支持多种视频格式的转码&#xff0c;并且支持常…