C++: 内存管理 (new / delete)

文章目录

  • 一. C/C++ 内存分布
  • 二. C 语言中动态内存管理方式: malloc/calloc/realloc/free
  • 三. C++内存管理方式
    • 1. new / delete 操作内置类型
    • 2. new / delete 操作自定义类型
  • 四. operator new 与 operator delete 函数
  • 五. new 和 delete 的实现原理
    • 1. 内置类型
    • 2. 自定义类型
  • 六. 定位 new 表达式 (placement-new)
  • 七. malloc/free 和 new/delete 的区别

一. C/C++ 内存分布

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

下面来看一段代码和相关内存分布问题

#define _CRT_SECURE_NO_WARNINGS   1
#include<iostream>
#include<string.h>
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";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);std::cout << "sizeof(num1):" <<sizeof(num1) << std::endl;std::cout << "sizeof(char2):"<<sizeof(char2) << std::endl;std::cout << "strlen(char2):" << strlen(char2) << std::endl;std::cout << "sizeof(pChar3):" << sizeof(pChar3) << std::endl;std::cout << "strlen(pChar4):" << strlen(pChar3) << std::endl;std::cout << "sizeof(ptr1):" << sizeof(ptr1) << std::endl;
}int main()
{Test();return 0;
}

在这里插入图片描述

  • globaVar 是全局变量, 所有文件都可以访问, 存放在数据段.
  • staticGlobalVar 是静态全局变量, 只有当前文件可以访问, 存放在数据段.
  • staticVar 是静态局部变量, 只有当前函数可以访问, 存放在数据段.
  • localVar 是局部变量, 存放在栈.
  • num1 是局部变量, 存放在栈.

在这里插入图片描述

  • char2 是数组名, 字符串"abcd"存放在常量区, 在创建数组的时候, 会拷贝一份 "abcd\0" 在栈空间, 首元素'a'的地址就是数组名的地址.在栈空间.
  • *char2 是对数组首元素地址的解引用, *char2 相当于数组首元素 a , 在栈空间.
  • pChar3 是一个指针变量, 指向了存放在数据区的常量字符串 "abcd", const 限定的是 *pChar3, 存放在栈.
  • *pChar3 是对pChar3 的解引用, 实际上就是数据区中的 a, 在数据段
  • ptr1 指向了是通过 malloc 后返回的空间的首地址, 但是 ptr1 本身是 main 函数临时创建的, 存放在栈空间
  • *ptr1 是动态开辟空间的第一块四字节大小的空间的数据, 存放在堆空间

在这里插入图片描述

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

void Test()
{int* p1 = (int*)malloc(sizeof(int));free(p1);int* p2 = (int*)calloc(4, sizeof(int));int* p3 = (int*)realloc(p2, sizeof(int) * 10);//free(p2);  // 这里需要free(p2)吗?free(p3);
}

这里 p2 不需要自己手动释放, realloc 如果申请空间成功, 会自动释放原来的空间.
在这里插入图片描述

  • malloccalloc 的唯一区别就是 calloc 会将申请空间初始化为 0, 而 malloc 则不会对申请到的空间进行初始化处理.
  • realloc: 如果原地址可以存放新开辟的空间, 则直接原地址进行扩容; 如果原地址没有那么大的空间, 则会新找到一块空间, 同时将原来的空间自动释放, 返回新空间的起始地址.

三. C++内存管理方式

C 语言内存管理方式可以在 C++继续使用, 但有些地方就无能为力, 而且使用起来比较麻烦, 因此 C++又提出了自己的内存管理方式: 通过 newdelete 操作符进行动态内存管理.

1. new / delete 操作内置类型

void Test()
{// 动态申请一个 int 类型的空间, 不会初始化int* ptr1 = new int;// 动态申请一个 int 类型的空间, 并初始化为 10int* ptr2 = new int(10);// 动态申请十个 int 类型的空间, 不会初始化int* ptr3 = new int[10];// 动态申请十个 int 类型的空间, 并按照列表初始化int* ptr4 = new int[10]{1, 2, 3};       //第四个元素开始被默认初始化为 0delete(ptr1);delete(ptr2);delete[](ptr3);delete[](ptr4);
}

注意:
申请和释放单个元素的空间, 使用 newdelete 操作符.
申请和释放连续的空间, 使用 new[]delete[] 操作符.
newmalloc 在用法上没有区别, 区别就是 new 可以在申请空间的时候同时对那一块空间进行初始化, 而 malloc 只可以申请空间.

2. new / delete 操作自定义类型

class A
{public:A(int a):_a(a){cout << "A(int a): " << this << endl;}~A(){cout << "~A(): " << this << endl;}private:int _a;
};int main()
{// new/delete 和 malloc/free 的最大区别就是 new/delete 对于自定义类型出了开空间和释放空间还会调用构造函数和析构函数A *ptr1 = (A*)malloc(sizeof(A));A *ptr2 = new A(1);free(ptr1);delete(ptr2);return 0;
}

结果如下:
在这里插入图片描述

newdelete 会调用自定义类型的构造函数和析构函数.
mallocfree 不会, 它们只会申请空间和释放空间.


new 一块连续的空间, 有几个元素就要调用几次构造函数和析构函数, 同时会按照先构造后析构的顺序进行.

// new[] / delete[]A* ptr3 = new A[3]{1, 2, 3};  // C++11 使用列表初始化delete[](ptr3);

在这里插入图片描述


下面有一个更为复杂一点的栈自定义类型

class Stack
{public:Stack(int capacity = 4):_capacity(capacity),_top(0){cout << "Stack(int capacity = 4)" << endl;_array = new int[_capacity];}~Stack(){cout << "~Stack()" << endl;delete[](_array);_array = nullptr;_capacity = _top = 0;}private:int* _array;int _capacity;int _top;
};int main()
{Stack* p1 = new Stack(5);delete(p1);return 0;
}

在这里插入图片描述

在这里插入图片描述

对于自定义类型, new/deletemalloc/free 的区别就是 new/delete 会分别调用构造函数和析构函数.

在这里, 直接 free(p1) 肯定是有问题的, free 在释放自定义类型对象空间前不会调用该自定义类型的析构函数, 那么 _array 指向的空间没有被释放, 造成了内存泄漏.


对于 new 操作, 如果 new 失败怎么处理呢?
在 C++ 中是使用捕捉异常来处理的, 之后会详细讲解, 这里简单展示以下用法.

void Test()
{char* p1 = new char[1024*1024*1024];cout << (void*)p1 << endl;              // char*类型不能直接使用cout, 会被自动识别成字符串char* p2 = new char[1024*1024*1024];cout << (void*)p2 << endl;
}int main()
{try{Test();}catch(const std::exception& e){cout << "1" << endl;std::cerr << e.what() << '\n';}return 0;
}

第二次开辟空间失败, 会直接捕捉到异常并打印出异常, 程序并不会崩.
在这里插入图片描述

四. operator new 与 operator delete 函数

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

operator newoperator delete 函数就是库全局函数, 是 mallocfree 的封装, 不调用构造函数和析构函数.

如果申请空间失败则会直接抛异常, 而原来的 malloc 只能返回 0.

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
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)

以下三种对于内置类型的动态开辟内存空间的操作是一样的.

int main()
{int* p1 = (int*)malloc(sizeof(int));free(p1);int* p2 = new int;delete(p2);int* p3 = (int*)operator new(sizeof(int));operator delete(p3);return 0;
}

在这里插入图片描述

在这里插入图片描述


那么下面的代码也能知道底层是怎么调用的了.

Stact* p1 = new Stack[10];
delete[](p1);

首先调用 operator new 开辟了 160 字节大小的空间, 随后调用了 10 次构造函数, 创造了 10 个 Stack 类的自定义类型对象.

但是通过调试却发现, 开辟了不是 160 字节, 而是 168 字节, 传入 operator new 的参数 sz168

在这里插入图片描述

开辟完空间后, p1 的值为 0x614c28 , 通过观察 0x614c20, 发现多开辟的 8 字节空间, 存放了数组元素个数 10

在这里插入图片描述

更换代码, Stack* p1 = new Stack[8];
发现 p1 指向地址的向前 8 个字节确实为 8

在这里插入图片描述

这个数字存在是因为自定义类型有析构函数, 需要知道要调用多少次析构函数.

这个时候, 如果我错误的使用了 delete 而非 delete[], 会直接导致程序崩溃, 程序真实是在 0x614c20 开始申请了 168 个字节大小的空间. 使用 delete[] 会自动把该地址传入 operator delete, 而 delete 不会这么处理, 会把原来的 0x614c28 传入函数, 这个地址不是申请空间的首地址, 释放失败.

在这里插入图片描述

但如果是内置类型, 则有可能不会崩溃

int* p2 = new int[10];
delete(p2);

内置类型没有析构函数, 也就不需要多开一块空间用来记录需要调用多少次析构函数.这样, 程序也就不会崩溃.

在这里插入图片描述


通过上面的调试, 可以得到下面的编程注意.

new/delete, new[]/delete[], malloc/free一定要配套使用, 否则结果是未确定的.

五. new 和 delete 的实现原理

1. 内置类型

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

2. 自定义类型

  • new 的原理

    1. 调用 operator new 函数构造空间
    2. 在申请的空间上执行构造函数, 完成对象的构造
  • delete 的原理

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

    1. 调用 operator new[] 函数, 在 operator new[] 中实际调用 operator new 函数完成 N 个对象空间的申请
    2. 在申请的空间上执行 N 次构造函数
  • delete[] 的原理

    1. 在释放的对象空间上执行 N 次析构函数, 完成 N 个对象中资源的清理
    2. 调用 operator delete[] 释放空间, 实际在 operator delete[] 中调用 operator delete 来释放空间

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

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

使用格式:

new(place_address)type 或者 new(place_address)type(initializer-list)place_address 必须是一个指针, initializer-list 是类型的初始化列表.

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

class A
{public:A(int a = 0):_a(a){cout << "A():" << this << endl;}~A(){cout << "~A(): " << this << endl;}private:int _a;
};int main()
{A* p1 = (A*)malloc(sizeof(A));          // 此时p1所指向的空间并没有被初始化, malloc只有申请空间的作用new(p1)A;   // 使用定位new进行p1对象的构造p1->~A();   // 可以显示调用析构函数free(p1);A* p2 = (A*)operator new(sizeof(A));    // operator new 同样只申请空间 并不初始化空间new(p2)A(2);  // 使用列表进行初始化p2->~A();operator delete(p2);return 0;
}

最后确实使用定位 new 完成了对未初始化空间的初始化构造

在这里插入图片描述

七. malloc/free 和 new/delete 的区别

malloc/freenew/delete 的共同点是: 都是从堆上申请空间, 并且需要用户手动释放.
不同的地方是:

  1. mallocfree 是函数, newdelete 是操作符.
  2. malloc 申请的空间不会初始化, new 可以初始化.
  3. malloc 申请空间时, 需要手动计算空间大小并传递, new 只需在其后跟上空间的类型即可, 如果是多个对象, [] 中指定对象个数即可.
  4. malloc 的返回值为 void*, 在使用时必须强转, new 不需要, 因为 new 跟的是空间的类型.
  5. malloc 申请空间失败时, 返回的是 NULL, 因此使用时必须判空, new 不需要, 但是 new 需要捕获异常.
  6. 申请自定义类型对象时, malloc/free 只会开辟空间, 不会调用构造函数和析构函数, 而 new 在申请空间后会调用构造函数完成对象的初始化, delete 在释放空间前会调用析构函数完成空间中资源的清理.

本章完.

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

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

相关文章

【中间件篇-Redis缓存数据库02】Redis高级特性和应用(慢查询、Pipeline、事务、Lua)

Redis高级特性和应用(慢查询、Pipeline、事务、Lua) Redis的慢查询 许多存储系统&#xff08;例如 MySQL)提供慢查询日志帮助开发和运维人员定位系统存在的慢操作。所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间&#xff0c;当超过预设阀值,就将这条命令的相关…

互联网大厂招兵买马开发鸿蒙应用,移动开发的春天又来了?

日前&#xff0c;美团拟开发鸿蒙系统APP的多个相关岗位正招聘开发人员引发业内关注。事实上&#xff0c;鸿蒙开发者已经成为京东、WPS、凤凰新闻、微博等互联网大厂争相招聘的人才&#xff0c;且招聘岗位众多。也就是说&#xff0c;这些公司正在加快鸿蒙化开发&#xff0c;为鸿…

基于C#+WPF编写的调用讯飞星火大模型工具

工具源码&#xff1a;https://github.com/lishuangquan1987/XFYun.SparkChat 工具效果截图&#xff1a; 支持流式输出: 其中ApiKey/ApiSecret/AppId需要自己到讯飞星火大模型官网去注册账号申请&#xff0c;免费的。 申请地址&#xff1a;https://xinghuo.xfyun.cn/ 注册之…

【OpenCV实现图像:用OpenCV图像处理技巧之白平衡算法2】

文章目录 概要Gray-world AlgotithmGround Truth Algorithm结论&#xff1a; 概要 随着数字图像处理技术的不断发展&#xff0c;白平衡算法成为了图像处理中一个关键的环节。白平衡的目标是校正图像中的颜色偏差&#xff0c;使得白色在图像中呈现真实的白色&#xff0c;从而提…

利用MSF设置代理

1、介绍&#xff1a; 通过MSF拿到一个机器的权限后&#xff0c;通过MSF搭建socks代理&#xff0c;然后通内网。 拿到目标权限&#xff0c;有很多方法&#xff0c;比如&#xff1a;①ms17-010 ②补丁漏洞 ③MSF生成后门 在此直接使用MSF生成后门 MSF中有三个代理模块&#x…

【ATTCK】MITRE Caldera - 测试数据泄露技巧

CALDERA是一个由python语言编写的红蓝对抗工具&#xff08;攻击模拟工具&#xff09;。它是MITRE公司发起的一个研究项目&#xff0c;该工具的攻击流程是建立在ATT&CK攻击行为模型和知识库之上的&#xff0c;能够较真实地APT攻击行为模式。 通过CALDERA工具&#xff0c;安全…

【函数讲解】pygmo中的函数 fast_non_dominated_sorting() + 利用支配关系,学习一个SVM分类器,将解分为两类

这个函数是用来执行非支配排序的&#xff0c;可以分层构建Pareto&#xff0c;并返回每一层的解以及每个解支配其他解的索引、解被其他解支配的次数、解所在的非支配层级。这个函数对这些解进行非支配排序&#xff0c;并返回四个数组&#xff1a;ndf, dl, dc, 和 ndr。 ndf (Non…

基于单片机设计的智能风扇(红外线无线控制开关调速定时)

一、项目介绍 在炎热的夏季&#xff0c;风扇成为人们室内生活中必不可少的电器产品。然而&#xff0c;传统的风扇控制方式存在一些不便之处&#xff0c;比如需要手动操作开关、无法远程控制和调速&#xff0c;以及缺乏定时功能等。为了解决这些问题&#xff0c;设计了一款基于…

红黑树-RBTree

目录 1. 红黑树的概念2. 红黑树的性质3. 结点的定义4. 结点的插入5. 整体代码 1. 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式…

长安链可验证数据库,保证数据完整性的可信存证方案

近日&#xff0c;长安链发布“可验证数据库”实现了链上链下协同存储及数据完整性保证&#xff0c;显著提升长安链存储能力的可扩展性。 可信存证是联盟链最典型的应用场景&#xff0c;被广泛应用在司法、工业、农业、贸易等领域。联盟链的存证应用主要分为两个阶段&#xff1…

通过easyexcel导出数据到excel表格

这篇文章简单介绍一下怎么通过easyexcel做数据的导出&#xff0c;使用之前easyui构建的歌曲列表crud应用&#xff0c;添加一个导出按钮&#xff0c;点击的时候直接连接后端接口地址&#xff0c;在后端的接口完成数据的导出功能。 前端页面完整代码 let editingId; let request…

【Python】一篇带你掌握数据容器之列表

目录 前言&#xff1a; 一、列表 1.列表的定义 2.列表的下标索引 3.列表的常用操作 &#xff08;1&#xff09;index方法&#xff1a;查找某元素的下标 (2)修改特定位置下标的元素 &#xff08;3&#xff09;insert&#xff08;下标&#xff0c;元素&#xff09;方法&a…

基于SpringBoot+Vue的在线学习平台系统

基于SpringBootVue的在线学习平台系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 用户界面 登录界面 管理员界面 摘要 本文设计并实现了一套基于Spri…

Nuxt.js——基于 Vue 的服务端渲染应用框架

文章目录 前言一、知识普及什么是服务端渲染什么是客户端渲染&#xff1f;服务端渲染与客户端渲染那个更优秀&#xff1f; 二、Nuxt.js的特点Nuxt.js的适用情况&#xff1f; 三、Vue是如何实现服务端渲染的&#xff1f;安装依赖使用vue安装 Nuxt使用npm install安装依赖包使用n…

基于springboot实现桥牌计分管理系统项目【项目源码】

基于springboot实现桥牌计分管理系统演示 JAVA简介 JavaScript是一种网络脚本语言&#xff0c;广泛运用于web应用开发&#xff0c;可以用来添加网页的格式动态效果&#xff0c;该语言不用进行预编译就直接运行&#xff0c;可以直接嵌入HTML语言中&#xff0c;写成js语言&#…

基于飞蛾扑火算法优化概率神经网络PNN的分类预测 - 附代码

基于飞蛾扑火算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于飞蛾扑火算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于飞蛾扑火优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

第一百七十一回 SearchBar组件

文章目录 1. 概念介绍2. 使用方法3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"Material3中的IconButton"相关的内容&#xff0c;本章回中将 介绍SearchBar组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在…

2023 ChinaJoy后,Flat Ads成为游戏、社交出海的新选择

今年ChinaJoy 展会&#xff0c;共吸引了来自世界各地的 500 多家企业参展&#xff0c;预计吸引超过33万人次参观。ChinaJoy年年有&#xff0c;那今年对于行业来说有什么新变化呢&#xff1f; 01 出海热潮不减&#xff0c;新增客户明显提升 据不完全统计&#xff0c;展会期间前…

《红蓝攻防对抗实战》十二.内网穿透之利用ICMP协议进行隧道穿透

内网穿透之利用ICMP协议进行隧道穿透 一.前言二.前文推荐三.利用ICMP协议进行隧道穿透1.ICMPsh获取反弹shell2.PingTunnel 搭建隧道 四.本篇总结 一.前言 本文介绍了利用ICMP协议进行隧道穿透的方法。ICMP协议不需要开放端口&#xff0c;可以将TCP/UDP数据封装到ICMP的Ping数据…

Error creating bean with name ‘apiModelSpecificationReader‘ defined in URL

问题&#xff1a; 启动项目的时候&#xff0c;报错了 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name apiModelSpecificationReader defined in URL [jar:file:/D:/.gradle/caches/modules-2/files-2.1/io.springfox/sp…