21天学会C++:Day13----动态内存管理

news2024/11/28 12:55:35

· CSDN的uu们,大家好。这里是C++入门的第十三讲。
· 座右铭:前路坎坷,披荆斩棘,扶摇直上。
· 博客主页: @姬如祎
· 收录专栏:C++专题

 

目录

1. 加深对内存四区的理解

2.  new-delete 与 malloc-free

2.1 能否用 free 释放 new 出来的空间

2.3 new 与 delete的底层实现 

3. 定位new / placement-new 


1. 加深对内存四区的理解

在学习完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);
}
选择题:
选项 : A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)

1:globalVar在哪里?____     

2:staticGlobalVar在哪里?____
3:staticVar在哪里?____     

4: localVar在哪里?____
5:num1 在哪里?____

6:char2在哪里?____       

7:*char2在哪里?___
8:pChar3在哪里?____   

9:* pChar3在哪里?____
10:ptr1在哪里?____         

11:* ptr1在哪里?____

1: globalVar 是一个全局变量,当然是在静态区的!

2:staticGlobalVar 是一个全局的静态变量是在静态区哦!

3:staticVar 是一个局部的静态变量,但还是在静态区哦!

4:localVar 是一个局部变量,当然是在栈区的!

5:num1 是一个局部的数组,当然也是在栈区的!

6:char2 是一个数组,当然是在栈区的,虽然 "abcd" 是一个字符串常量,但是使用字符串常量初始化数组,起始就是将每一个字符拷贝到栈区,然后给数组初始化的!

7:*char2 代表字符数组的第一个字符,当然是在栈区的

8:pChar3 在栈区啊!字符串常量是一个地址,pChar3 指向了这个地址,但 pChar3 这个变量本身还是在栈区的。

9:*pChar3 代表字符串常量的第一个字符,当然是在常量区的!

10:ptr1 是一个局部变量,指向堆区开辟的空间,是在栈区的!

11:*ptr1 代表整形数组的第一个元素,malloc 开辟的空间是在堆区的,因此 *ptr1 就是在堆区的!

昨晚这些练习题之后来总结一下吧:

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

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

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

4. 数据段--存储全局数据和静态数据。

5. 代码段--可执行的代码/只读常量。

2.  new-delete 与 malloc-free

还记得我们使用C语言的时候是怎么向堆区申请空间的吗?我们都是使用malloc或者calloc这两个的函数。可是malloc和calloc有什么缺陷呢?

我们定义了一个类 A 代表自定义类型,然后我们用 malloc 向堆区 申请一个 A 大小的空间和一个整形大小的空间,看看申请之后的结果!

class A
{
public:
	A(int x = 0)
		:_x(x)
	{}

private:
	int _x;
};


int main()
{

	A* a = (A*)malloc(sizeof(A));
	int* _int = (int*)malloc(sizeof(int));

	return 0;
}

 我们发现对于malloc,无论是内置类型还是自定义类型,都没有进行初始化,这就不符合我们的预期了!我们想的是malloc对于自定义类型能够调用他的构造函数就好了!但是malloc表示:臣妾做不到啊!那calloc能否满足我们的需求呢?显然也是不能的,calloc只能暴力地将申请 出来的空间初始化为0!

C++的祖师爷也是看malloc,calloc非常不爽啊!于是定义了两个关键字:new,delete用于动态内存的分配和管理!我们来看看new分别会对内置类型和自定义类型做什么样的处理呢?

 

可以看到在我们不手动初始化的时候,对于自定义类型,会去调用他的默认构造函数;对于内置类型没有做处理。

那我们想要根据自己的需求初始化应该怎么做呢?我们只需要加一个括号然后传入我们想要初始化的值哦!

注意:

对于自定义类型加括号传值初始化本质都是调用对应的构造函数,乳沟你没有对应的构造函数时要报错的哦!

对于内置类型加括号就是直接初始化了! 

new 出来的空间记得要用delete释放哦!

delete 指针;

那我们要开一个自定义类型的数组,或者内置类型的数组应该怎么做呢?

new 类型[数量];


int main()
{

	A* a = new A[10];
	int* _int = new int[10];


	delete[] a;
	delete[] _int;

	return 0;
}

我们不做初始化的话,对于自定义类型还是回去调用他的默认构造函数,对于内置类型还是不会作处理!

那么我们该如何手动初始化呢?我们只需要在后面加上一个大括号,然后填上你想要初始化的值就可以了!

int main()
{

	A* a = new A[5]{ A(1), A(2), A(3) };
	int* _int = new int[5] {1, 2, 3, 4};


	delete[] a;
	delete[] _int;

	return 0;
}

 大括号里面如果只进行了部分内存的初始化,对于内置类型和C语言一样未手动初始化的内存会自动初始化为 0 ;对于自定义类型当然是调用他的默认构造函数啦!

注意:如果你使用 new[] 申请数组,那么释放空间的时候就必须 delete [] 。必须对应使用。 

我们已经知道了使用new关键字在堆上申请空间时对于自定义类型会调用他的构造函数,那么对于delete会对自定义类型作何处理呢?

我们在构造函数和析构函数的函数体内写上打印语句代表调用了构造或析构函数:

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

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

private:
	int _x;
};


int main()
{
	A* a1 = new A[5]{ A(1), A(2) };
	delete[] a1;

	return 0;
}

 

可以看到:我们并没有显示的调用析构函数,但是析构函数确实是被调用了的!那这肯定是delete搞的鬼! 我们现在就要弄清楚是先delete释放空间还是先调用析构函数!你可以仔细想想,答案自然就出来了!如果你还不确定就来看看下面的代码来帮助你理解:

我们定义了一个Stack类,在构造函数中为 _a 在堆区上开辟了空间,在析构函数中释放了这块空间。在 main 函数中我们在堆区上开辟了一个对象大小的空间。

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		_size = 0;
		_capacity = capacity;
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
	}

private:
	int* _a;
	int _size;
	int _capacity;
};


int main()
{
	Stack* st = new Stack(10);
    delete st;
	return 0;
}

我们通过画图来理解这段程序的内存分配:

假设我们delete的时候是先释放通过new申请的空间,那么必然会造成内存泄漏,因为析构函数已经无法做到释放通过malloc申请的空间。(delete之后内存会变成随机值)因此在delete的时候一定是先调用的析构函数,释放成员变量维护的空间,然后再释放new申请的空间。 

2.1 能否用 free 释放 new 出来的空间

你可能会突发奇想,要是我用free来释放new出来的空间,或者使用delete释放malloc出来的空间会怎么样呢?话不多说我们直接来试一试!

我们先来用内置类型试试:

int main()
{
	// new一个整形 用free释放
	int* _int1 = new int;
	free(_int1);

	//malloc 一个整形用 delete释放
	int* _int2 = (int*)malloc(sizeof(int));
	delete _int2;

	//new 一个整形数组 用 free 释放
	int* _int3 = new int[10];
	free(_int3);

	//malloc 一个整形数组 用delete释放
	int* _int4 = (int*)malloc(sizeof(int) * 10);
	delete _int4;

	return 0;
}

我们可以看到,对于内置类型 这两个可以混合使用,没有啥问题。但是编译器会报警告

 现在来看看自定义类型呢:

int main()
{
	// new一个自定义类型 A 用free释放
	A* a1 = new A[10];
	free(a1);

	//malloc 一个自定义类型A 用 delete 释放
	A* a2 = (A*)malloc(sizeof(A) * 10);
	delete[] a2;

	return 0;
}

 我们看到自定义类型似乎也没有问题!

无论是 malloc 开空间 delete 释放空间,还是 new 开空间 free 释放空间,这是否被允许完全是由编译器决定的!上面我们的实验环境是 VS2022,但是在 VS2019 对于自定义类型就会报错了!

为了适应不同平台的需求,我们应该遵守:malloc - free,new - delete 相对应的原则

2.3 new 与 delete的底层实现 

在C++中有这样两个全局函数 operator new 与 operator delete,里面隐藏着 new 与 delete 的底层实现。这里的 operator new 与 operator delete 不是重载的 new 与 delete哈:


void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
    // try to allocate size bytes
    void* p;
    while ((p = malloc(size)) == 0)

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);  /* block other threads */
    __TRY
        /* get a pointer to memory block header */
        pHead = pHdr(pUserData);
    /* verify block type */
    _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
    _free_dbg(pUserData, pHead->nBlockUse);
    __FINALLY
        _munlock(_HEAP_LOCK);  /* release other threads */
    __END_TRY_FINALLY
        return;
}
/*
free的实现
*/
#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)

上面的代码就是 operator new 与 operator delete 的实现。起始 new 与 delete 这两个关键字就是通过调用 operator new 与 operator delete 来实现向堆区申请空间的!仔细观察 operator new 发现它居然是 通过 malloc 来申请空间的!仔细观察 operator delete 发现他是通过 _free_dbg 来释放空间的,free 其实是一个宏函数,他也是调用 _free_dbg 这个函数来释放空间的!因此 operator delete 可以理解为就是通过delete来释放空间的!

观察operator new 发现它开辟空间失败是抛异常,并不像malloc 那样返回空指针。

这些都是可以通过汇编代码和调试信息验证的哦!

我们现在来看 new 空间失败是否是抛异常的呢?

我们通过while循环一直开空间,知道开空间失败,抛出异常,然后我们不服偶这个异常并打印异常信息:

int main()
{
	try
	{
		while (1)
		{
			int* a = new int[1024 * 1024];
			cout << a << endl;
		}
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
}

我们打印的异常信息:bad allocation 很明显new 空间失败的确是通过抛异常来处理的 。

 下面我们来证明new关键字的却是先调用 operator new 在调用构造函数,delete 关键字 是先调用 析构函数再调用operator delete的。

通过调试程序转到汇编代码我们看到new的确是先调用operator new 再调用 构造函数的:

 通过调试程序转到汇编代码我们看到delete的确是先调用 析构函数 在调用 operator delete的:

3. 定位new / placement-new 

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

使用格式: new (指针) 类型。

举个例子:我们通过malloc像堆区申请了一块空间,但是我们知道malloc并不会初始化这一块空间于是我们就可以通过定位new 指定调用构造函数来初始化这块空间。

class A
{
public:
	A(int x = 0)
		:_x(x)
	{}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _x;

};

int main()
{
	A* a = (A*)malloc(sizeof(A));
	new (a)A(10);

	delete a;
}

因为要求括号内必须是指针,又因为这是内置类型,所以我们必须显示调用析构函数才能释放成员变量维护的堆区空间。

a->~A(); //显示调用析构函数

使用场景: 定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如 果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。 

 

 

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

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

相关文章

Django:一、创建项目、APP及启动Django

一、准备工具 Pycharm企业版 二、创建项目 打开Pycharm企业版&#xff0c;创建Django项目。 注意&#xff1a;①删除项目下的templates文件夹&#xff1b;②删除setting.py文件中的一行代码 默认文件介绍&#xff1a; 三、创建APP 点击Pycharm左下角Terminal&#xff0c;打…

基于Android+OpenCV+CNN+Keras的智能手语数字实时翻译——深度学习算法应用(含Python、ipynb工程源码)+数据集(四)

目录 前言总体设计系统整体结构图系统流程图 运行环境模块实现1. 数据预处理2. 数据增强3. 模型构建4. 模型训练及保存5. 模型评估6. 模型测试1&#xff09;权限注册2&#xff09;模型导入3&#xff09;总体模型构建4&#xff09;处理视频中的预览帧数据5&#xff09;处理图片数…

区块链技术:解密去中心化的革命

文章目录 区块链的基础概念什么是区块链&#xff1f;区块链的核心原理1. 分布式账本2. 区块3. 加密技术4. 共识机制 区块链的工作原理区块链的交易过程区块链的安全性共识机制的作用 区块链的应用领域1. 金融服务2. 供应链管理3. 物联网4. 医疗保健5. 政府与公共服务 区块链的未…

图像练习OpenCV(01)

提取出里面最大矩形的四个顶点坐标 源图像 结果展示 代码 void getLine(std::vector<int>& data, int threshold) {for (int x 0; x < data.size(); x){if (0 data[x]){continue;}int maxValue 0, maxLoc -1, i -1;for (i x; i < data.size(); i){if …

【2023集创赛】Arm杯二等奖作品:基于Arm Cortex-M3的体感节奏音乐游戏机

本文为2023年第七届全国大学生集成电路创新创业大赛&#xff08;“集创赛”&#xff09;安谋科技杯全国二等奖作品分享&#xff0c;参加极术社区的【有奖征集】分享你的2023集创赛作品&#xff0c;秀出作品风采&#xff0c;分享2023集创赛作品扩大影响力&#xff0c;更有丰富电…

跑腿系统开发:构建实时任务分配算法的技术挑战

在跑腿系统中&#xff0c;实时任务分配算法是确保任务快速高效完成的关键因素之一。本文将介绍构建实时任务分配算法时可能面临的技术挑战&#xff0c;并提供一个简单的Python示例来解决这些挑战。 技术挑战&#xff1a; 实时数据处理&#xff1a; 跑腿系统需要处理大量的实时任…

Ganache本地测试网+cpolar内网穿透实现公网访问内网

文章目录 前言1. 本地环境服务搭建2. 局域网测试访问3. 内网穿透3.1 ubuntu本地安装cpolar内网穿透3.2 创建隧道3.3 测试公网访问 4. 配置固定二级子域名4.1 保留一个二级子域名4.2 配置二级子域名4.3 测试访问公网固定二级子域名 前言 网&#xff1a;我们通常说的是互联网&am…

K8S:Pod概念、分类及相关的策略

文章目录 一.pod相关概念&#xff11;.Pod基础概念&#xff12;.Kubrenetes集群中Pod两种使用方式&#xff13;.pause容器的Pod中的所有容器共享的资源&#xff14;.kubernetes中的pause容器主要为每个容器提供功能&#xff1a;&#xff15;.Kubernetes设计这样的Pod概念和特殊…

本地搭建CFimagehost私人图床——“cpolar内网穿透”

文章目录 1.前言2. CFImagehost网站搭建2.1 CFImagehost下载和安装2.2 CFImagehost网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测…

7.algorithm2e中while怎么使用

algorithm2e中while怎么使用 在 algorithm2e 宏包中&#xff0c;要使用 while 循环&#xff0c;您可以使用 \While 和 \EndWhile 命令来定义循环的开始和结束。以下是如何使用 while 循环的示例&#xff1a; \documentclass{article} \usepackage[linesnumbered,boxed]{algorit…

Mac电脑音视频播放器: Infuse for Mac中文

Infuse是一款流行的多媒体播放器应用程序&#xff0c;适用于iOS、tvOS和macOS平台。它由Firecore开发&#xff0c;旨在提供出色的媒体播放体验&#xff0c;并支持广泛的视频和音频格式。 以下是Infuse的一些主要功能和特点&#xff1a; 多媒体格式支持&#xff1a;Infuse支持…

ROS2 从头开始​​:第 1 部分 — 机器人操作系统简介

火星上的机器人&#xff08;AI生成图像&#xff09; 一、说明 ROS2是机器人的朋友&#xff0c;一个他们所依赖的平台&#xff0c;用于沟通、协调和控制&#xff0c;帮助他们实现目标。ROS2以DDS为核心&#xff0c;帮助机器人探索新世界、新任务、新可能性&#xff0c;是一个方…

代码随想录--链表-反转链表

题意&#xff1a;反转一个单链表。 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL 双指针 public class Main {public static class ListNode {int val;ListNode next;ListNode(int x) {val x;}}public ListNode reverseList(L…

涨知识,关于代码签名证书10大常见问题解答

在当今互联网时代&#xff0c;各种软件程序充斥着这个网络世界&#xff0c;大大小小的软件层出不穷&#xff0c;如何让用户信任软件并下载软件&#xff0c;是众多软件开发公司需要解决的问题&#xff0c;由此代码签名证书应运而生&#xff0c;提供了软件程序的身份认证、完整性…

uni-app获取元素具体位置获取失败

场景&#xff1a;想要通过链接跳转传递catid&catid2类别id,商品类别id 跳到这一页左侧对应的类别栏上面,同时跳到右侧列表滚动到对应商品那一块区域。 遇到的问题&#xff1a;在for循环中通过绑定id获取不到商品列表的具体位置。 原因&#xff1a;在onReady函数和mounted函…

【Java】医院智能导诊系统源码:解决患者盲目就诊问题、降低患者挂错号比例

医院智能导诊系统解决患者盲目就诊问题&#xff0c;减轻分诊工作压力。降低患者挂错号比例&#xff0c;优化患者就诊流程&#xff0c;有效提高线上线下医疗机构接诊效率。患者可通过人体画像选择症状部位&#xff0c;了解对应病症信息和推荐就医科室。 一、医院智能导诊系统概述…

科锐逆向第二阶段(一)SDK

基本概念 什么是 SDK SDK 是软件开发工具包&#xff08;Software Development Kit&#xff09;的缩写。它是一个集成了软件开发所需工具、库文件、示例代码和文档等资源的软件包。 SDK 通常由软件开发公司或平台提供&#xff0c;旨在帮助开发人员构建、测试和部署特定类型的…

Offset Explorer(Kafka消息可视化工具)报invalid hex digit ‘{‘错误解决方法

解决办法&#xff1a; 根据代码的实际情况&#xff0c;设置成对应的值。设置完成后点update、refresh更新。

excel 通过SUMIF关键词统计词频

经常会对句子中含有的某些词汇数量进行统计&#xff0c;excel 也可以实现初级的操作 比如有如下文本 想要统计旅游和好两个词在这些文本中出现了多少次 用如下函数即可 SUMIF(A:A,"*"&C2&"*",B:B) 可以很方便的统计出好出现了3次数据&#xff0…

Python爬虫有哪些库,分别怎么用

目录 Python常用爬虫库 代码示例 requests BeautifulSoup Scrapy Selenium PyQuery Axios requests-html pyppeteer 总结 Python是一种非常流行的编程语言&#xff0c;因其易学易用和广泛的应用而受到开发者的喜爱。在Python中&#xff0c;有许多库可以用于爬虫程序…