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

· 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 bytesvoid* 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_FINALLYreturn;
}
/*
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.rhkb.cn/news/137821.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

滚雪球学Java(31):玩转多维数组:高效访问和遍历

&#x1f3c6;本文收录于「滚雪球学Java」专栏&#xff0c;专业攻坚指数级提升&#xff0c;助你一臂之力&#xff0c;带你早日登顶&#x1f680;&#xff0c;欢迎大家关注&&收藏&#xff01;持续更新中&#xff0c;up&#xff01;up&#xff01;up&#xff01;&#xf…

离散数学 学习 之一阶逻辑的前束范式

敲重点 如果是蕴含式的前件要改变符号&#xff0c;后件不需要

uniapp选择地址弹窗组件

1.效果 2.子组件在components里面创建组件AddreessWindow <template><view style"position: relative;z-index: 999999 !important;"><view class"address-window" :class"value true ? on : "><view class"title…

【LeetCode每日一题合集】2023.9.11-2023.9.17(⭐反悔贪心拓扑排序Floyd)

文章目录 630. 课程表 III解法——反悔贪心⭐⭐⭐⭐⭐ 1462. 课程表 IV⭐解法1——拓扑排序预处理解法2——Floyd算法判断是否存在路径 2596. 检查骑士巡视方案&#xff08;方向模拟&#xff09;1222. 可以攻击国王的皇后&#xff08;方向模拟&#xff09;LCP 50. 宝石补给&…

Leetcode 106. 从中序与后序遍历序列构造二叉树

文章目录 题目代码&#xff08;9.18 首刷自解&#xff09; 题目 Leetcode 106. 从中序与后序遍历序列构造二叉树 代码&#xff08;9.18 首刷自解&#xff09; class Solution { public:unordered_map<int, int> val2Index;TreeNode* buildTree(vector<int>& …

C++运算符优先级一览表

VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff09;https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&a…

开发需知的文件加密与解密

背景 最近团队遇到一个小需求&#xff0c;存在两个系统 A、B&#xff0c;系统 A 支持用户在线制作皮肤包&#xff0c;制作后的皮肤包用户可以下载后&#xff0c;导入到另外的系统 B 上。皮肤包本身的其实就是一个 zip 压缩包&#xff0c;系统 B 接收到压缩包后&#xff0c;解压…

dvwa靶场通关(十二)

第十二关&#xff1a;Stored Cross Site Scripting (XSS)&#xff08;存储型xss&#xff09; low 这一关没有任何防护&#xff0c;直接输入弹窗代码 弹窗成功 medium 先试试上面的代码看看&#xff0c;有没有什么防护 发现我们的script标签不见了&#xff0c;应该是被过滤掉…

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

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

Windows Server 2008安装.NET Framework 3.5

安装.NET Framework 3.5一、打开服务器管理器 在开始菜单中搜索“服务器管理器” 二、添加.NET Framework 3.5.1功能 &#xff08;一&#xff09;功能-》添加功能 &#xff08;二&#xff09;选择功能“.NET Framework 3.51” 1.点击“NET Framework 3.5.1”勾选框 2.点击“添…

C#小知识

项目编译后复制文件到生成目录 方法1 对于单个文件&#xff0c;可以点击属性。输出目录里选择始终复制。 方法2 把项目中的ServerScripts复制到输出目录。 在项目设置中&#xff0c;生成事件里添加批处理 xcopy $(ProjectDir)ServerScripts\*.* $(TargetDir)ServerScrip…

本地搭建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.公网访问测…

【JVM】Java类的加载机制!

一、类的生命周期 类加载过程包含&#xff1a;加载、验证、准备、解析和初始化 &#xff0c;一共包括5 个阶段。 &#xff08;1&#xff09;加载&#xff1a; 简单来说就是将java类的字节码文件加载到机器内存中。在加载类时&#xff0c;Java虚拟机必须完成以下3件事情&…

[Linux打怪升级之路]-缓冲区

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 本期学习目标&…

AI助力安全监管:TSINGSEE视频智能分析系统烟火识别算法

水火无情人有情&#xff0c;火灾一旦发生没有被及时发现&#xff0c;就能在极短的时间内酿成无法挽回的大祸&#xff0c;所以烟火的监管与处理极为重要。为了让火患在刚发生时就能得到扼制&#xff0c;TSINGSEE青犀AI智能分析网关烟火识别算法具有重要意义。 TSINGSEE青犀AI智能…

Arcgis栅格转点时ERROR 999999: 执行函数时出错。 无法创建要素数据集。 执行(RasterToPoint)失败

Arcgis栅格转点时ERROR 999999: 执行函数时出错。 无法创建要素数据集。 执行(RasterToPoint)失败。 问题描述 原因 输出点要素的位置不对 解决方案 点击新建文件地理数据库 然后在该文件地理数据库下输出

.Net IDE智能提示汉化(.Net6、AspNetCore)

先上现成的.net6汉化文件&#xff0c;可以手动下载后参照 如何为 .NET 安装本地化的 IntelliSense 文件 进行安装。或者使用后文的工具进行自动安装。 无对照英文在前中文在前 汉化内容来自 官方在线文档 &#xff0c;某些内容可能存在明显的机翻痕迹。 上一些效果图&#x…

UINT64整型数据在格式化时使用了不匹配的格式化符%d导致其他参数无法打印的问题排查

目录 1、问题描述 2、格式化函数内部解析待格式化参数的完整机制说明 2.1、传递给被调用函数的参数是通过栈传递的 2.2、格式化函数是如何从栈上找到待格式化的参数值&#xff0c;并完成格式化的&#xff1f; 2.3、字符串格式化符%s对应的异常问题场景说明 2.4、为了方便…

项目实战— pytorch搭建CNN处理MNIST数据集

项目文件夹介绍 项目文件夹 CNN_MNIST_practice文件夹是整个项目的文件夹&#xff0c;里面存放了六个子文件夹以及四个 .py 程序&#xff0c;接下来我们分别来介绍这些文件的内容。 其中 minist_all_CPU.py 是CPU版本的模型训练&#xff0b;测试程序&#xff0c;而 min…

【Redis】Redis的特性和应用场景 · 数据类型 · 持久化 · 数据淘汰 · 事务 · 多机部署

【Redis】Redis常见面试题&#xff08;3&#xff09; 文章目录 【Redis】Redis常见面试题&#xff08;3&#xff09;1. 特性&应用场景1.1 Redis能实现什么功能1.2 Redis支持分布式的原理1.3 为什么Redis这么快1.4 Redis实现分布式锁1.5 Redis作为缓存 2. 数据类型2.1 Redis…