C++ malloc/free/new/delete详解(内存管理)

C++ malloc/free/new/delete详解(内存管理)

  • malloc/free
    • 典型用法
    • 内存分配
    • 实现过程
      • brk和mmap
      • 申请小于128k的内存
      • 申请大于128k的内存
      • 释放内存
      • brk和mmap的区别
  • new/delete
    • 典型用法
  • 内存分配
  • 实现过程
  • new/delete和malloc/free的区别
  • malloc对于给每个进程分配的内存是不是有大小限制
  • delete [] 怎么知道要销毁多少内存空间
  • malloc的内存可以用delete释放吗?
  • new[]分配的空间可以用free()释放吗?
  • new[]和delete配对使用会发生什么
  • malloc出来20字节内存,为什么free不需要传入20呢,不会产生内存泄漏吗?
  • 限制对象只能建立在堆上
  • 限制对象只能建立在栈上

malloc/free

典型用法

malloc()负责动态配置内存,大小由size决定,分配成功时返回值为任意类型指针,指向一段可用内存(虚拟内存)的起始地址。分配失败时为NULL。

void * malloc(size_t size)

free()负责释放动态申请的内存空间,调用free()后ptr所指向的内存空间被收回,如果ptr指向未知地方或者指向的空间已被收回,则会发生不可预知的错误,如果ptr为NULL,free不会有任何作用。

void  free(void *ptr)

内存分配

malloc函数动态申请的内存空间是在堆里(而一般局部变量存于栈里),并且该段内存不会被初始化,如果不采用手动free()加以释放,则该段内存一直存在,直到程序退出才被系统,所以为了合理使用内存,在不适用该段内存时,应该调用free()。另外,如果在一个函数里面使用过malloc,最好要配对使用free,否则容易造成内存泄露。

实现过程

brk和mmap

从操作系统角度来看,malloc的实现有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。

  1. 申请小于128k的内存时,使用brk分配内存,将数据段.data的最高地址指针_edata向高地址移动,即增加堆的有效区域来申请新的内存空间。
  2. 申请大于128k的内存时,使用mmap分配内存,mmap是在进程的文件映射区找一块空闲存储空间,128K限制可由M_MMAP_THRESHOLD选项进行修改。

这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系

申请小于128k的内存

申请小于128k的内存时,使用brk分配内存,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系),如下图:
在这里插入图片描述

  1. 进程启动的时候,其(虚拟)内存空间的初始布局如图1。其中,mmap内存映射文件是在堆和栈的中间(例如libc-2.2.93.so,其它数据文件等),为了简单起见,省略了内存映射文件。_edata指针(glibc里面定义)指向数据段的最高地址。
  2. 进程调用A=malloc(30K)以后,内存空间如图2。malloc函数会调用brk系统调用,将_edata指针往高地址推30K,就完成虚拟内存分配。然而,_edata+30K只是完成虚拟地址的分配,A这块内存现在还是没有物理页与之对应的,等到进程第一次读写A这块内存的时候,发生缺页中断,这个时候,内核才分配A这块内存对应的物理页。也就是说,如果用malloc分配了A这块内容,然后从来不访问它,那么,A对应的物理页是不会被分配的。
  3. 进程调用B=malloc(40K)以后,内存空间如图3。

申请大于128k的内存

申请大于128k的内存时,使用mmap分配内存,在堆和栈之间找一块空闲内存分配,如下图:
在这里插入图片描述

  1. 进程调用C=malloc(200K)以后,内存空间如图4。默认情况下,malloc函数分配内存,如果请求内存大于128K(可由M_MMAP_THRESHOLD选项调节),那就不是去推_edata指针了,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存。这样子做主要是因为brk分配的内存需要等到高地址内存释放以后才能释放(例如,在B释放之前,A是不可能释放的,这就是内存碎片产生的原因,什么时候紧缩看下面),而mmap分配的内存可以单独释放。
  2. 进程调用D=malloc(100K)以后,内存空间如图5。

释放内存

  1. 进程调用free©以后,C对应的虚拟内存和物理内存一起释放,如图6。
  2. 进程调用free(B)以后,如图7所示。B对应的虚拟内存和物理内存都没有释放,因为只有一个_edata指针,如果往回推,那么D这块内存怎么办呢?当然,B这块内存,是可以重用的,如果这个时候再来一个40K的请求,那么malloc很可能就把B这块内存返回回去了。
  3. 进程调用free(D)以后,如图8所示。B和D连接起来,变成一块140K的空闲内存。

默认情况下:当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)。在上一个步骤free的时候,发现最高地址空闲内存超过128K,于是内存紧缩,变成图9所示。
在这里插入图片描述

brk和mmap的区别

  1. malloc 通过 brk() 方式申请的内存,free 释放内存的时候,并不会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用;同时brk分配的内存需要等到高地址内存释放以后才能释放,这也是内存碎片产生的原因
  2. malloc 通过 mmap() 方式申请的内存,free 释放内存的时候,会把内存归还给操作系统,内存得到真正的释放。除此之外,mmap分配的内存可以单独释放。

new/delete

典型用法

new和delete是C++中的运算符,不是库函数,不需要库的支持,同时,他们是封装好的重载运算符,并且可以再次进行重载。

new是动态分配内存的运算符,自动计算需要分配的空间,在C++中,它属于重载运算符,可以对多种数据类型形式进行分配内存空间,比如int型、char型、结构体型和类等的动态申请的内存分配,分配类的内存空间时,同时调用类的构造函数,对内存空间进行初始化,即完成类的初始化工作。new运算符的使用示例:

new int  //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址
new int(100)  //同上,并指定该整数的初值为100
new char[100] //开辟一个存放字符数组(100个元素)的空间,返回首地址
new int[4][5]//开辟一个存放二维数组的空间,返回首元素的地址
float *p=new float(3.14157) //开辟一个存放单精度的空间,并指定该数的初值为3.14157,将返回的该空间的地址赋给指针变量p

注意:用new分配数组空间不能指定初值,若无法正常分配,则new会返回一个空指针NULL或者抛出bad_alloc异常

delete是撤销动态申请的内存运算符。delete与new通常配对使用,与new的功能相反,可以对多种数据类型形式的内存进行撤销,包括类,撤销类的内存空间时,它要调用其析构函数,完成相应的清理工作,收回相应的内存资源。delete运算符的使用示例:

//注意,指针p存于栈中,p所指向的内存空间却是在堆中。
int *p = new intdelete p;
char *p = new chardelete p;
//注意,new申请数组,delete删除的形式需要加括号“[ ]”,表示对数组空间的操作,总之,申请形式如何,释放的形式就如何。
Obj * p = new Obj[100];               delete [ ]p;

内存分配

new申请的内存也是存于堆中,所以在不需要使用时,需要delete手动收回。

实现过程

在new一个对象的时候,首先会调用operator new() 为对象分配内存空间,然后调用对象的构造函数。

delete会调用对象的析构函数,然后调用free回收内存。

new/delete和malloc/free的区别

  1. new从自由存储区上分配内存,malloc从堆上分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配。自由存储区是否能够是堆取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。
  2. new 可以调用对象的构造函数,对应的 delete 调用相应的析构函数;malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数。在new一个对象的时候,底层首先调用 operator new() 函数为对象分配内存空间,然后调用对象的构造函数。delete会调用对象的析构函数,然后调用free回收内存。
  3. 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算;使用malloc则需要显式地指出所需内存的尺寸。
  4. new、delete 返回的是某种数据类型指针;malloc、free 返回的是 void 指针。
  5. new、delete 是操作符;malloc、free 是函数。
  6. malloc分配失败返回NULL;new要求在内存分配失败时要求返回NULL或抛出std::bad_alloc异常。

malloc对于给每个进程分配的内存是不是有大小限制

Windows下32位程序如果单纯看地址空间能有4G左右的内存可用,不过实际上系统会把其中2G的地址留给内核使用,32位Linux是用户3G+内核1G。所以你的程序最大能用2G(Windows)或者3G(Linux)的内存。除去其他开销,你能用malloc申请到的内存只有1.9G或者2.9G左右。

delete [] 怎么知道要销毁多少内存空间

new的执行过程:先给定需要的内存大小,调用operator new,在那里面获得制定大小的内存并返回;然后才以刚才返回的内存为基础调用类的构造函数。如果使用的是new[]来生成对象数组,需要多申请sizeof(int)(即4个字节)的空间来存储对象个数,以确定析构的次数

delete的执行过程:如果需要删除的是对象数组,首先要根据数组最开头的int数值来调用若干次析构函数;然后才释放存储空间。

这告诉我们,可以认为new就是malloc的封装。并且也解释了为什么new[]分配的空间用free()释放会出错(因为new[]分配空间返回的地址并不是它里面malloc分配空间的首地址,系统预留了sizeof(int)个字节)。

malloc的内存可以用delete释放吗?

可以,但是一般不这么用。malloc/free是c语言中的函数,c++为了兼容c保留下来这一对函数。简单来说,new 可以理解为,先执行malloc来申请内存,后调用构造函数来初始化对象;delete是先执行析构函数,后使用free来释放内存。

new[]分配的空间可以用free()释放吗?

不可以,因为new[]分配空间返回的地址并不是它里面malloc分配空间的首地址,系统预留了sizeof(int)个字节用来确定调用析构函数的次数。

new[]和delete配对使用会发生什么

  1. 如果数组中的元素类型为内置类型,调用delete时不需要析构函数,所以也就不需要多4个字节来存放掉调用析构函数的次数,所以不会报错。
  2. 如果数组中的元素类型为自定义类型,则delete只会析构数组中的第一个对象。
#include <stdlib.h>
#include <iostream>
using namespace std;int main() {int *pint = new int(5);delete[] pint;int *pinta = new int[4];delete pinta;cout << "success" << endl;return 0;
}
程序输出:
success

这段代码即使不配对使用也会正常运行,因为int是内置类型,调用delete[]时不需要析构函数,所以也就不需要多4个字节来存放数组长度,只需要直接操作内存即可。

malloc出来20字节内存,为什么free不需要传入20呢,不会产生内存泄漏吗?

这是因为虽然你告诉了malloc你要多少空间,但malloc真正分配了多少只有它自己知道。例如,你向malloc要了999字节,但某人写的malloc分配的最小粒度是1024字节,那么你会得到一个1024字节的空间。

在malloc时,所分配的不仅是你请求的那点空间,还加了一个信息块来记录额外信息,这个信息块位于你请求的空间前面。而malloc返回指针的指向的是你请求的空间,如果你想看看那个信息块的话,把malloc返回的指针往前走几步就能看到了。free所需的信息可以直接在信息块中取。信息块和空间都会被释放

限制对象只能建立在堆上

最直观的思想:避免直接调用类的构造函数,因为对象静态建立时,会调用类的构造函数创建对象。但是直接将类的构造函数设为私有并不可行,因为当构造函数设置为私有后,不能在类的外部调用构造函数来构造对象。但是由于 new 创建对象时,底层也会调用类的构造函数,将构造函数设置为私有后,那就无法在类的外部使用 new 创建对象了

首先我们想到的是将析构函数设置为私有。这是因为静态对象建立在栈上,是由编译器分配和释放内存空间,当析构函数设为私有时,编译器创建的对象就无法通过访问析构函数来释放对象的内存空间,因此,编译器不会在栈上为对象分配内存

但是该方法存在两个问题:

  1. 用 new 创建的对象,通常会使用 delete 释放该对象的内存空间,但此时类的外部无法调用析构函数,因此类内必须定义一个 destory() 函数用来释放 new 创建的对象
  2. 无法解决继承问题,因为如果这个类作为基类,析构函数要设置成 virtual,然后在派生类中重写该函数。但此时析构函数是私有的,派生类中无法访问

因此有了下面这个解决方法:将构造函数和析构函数设置为 protected,并提供一个 public 的静态函数来完成构造,而不是在类的外部使用 new 构造

限制对象只能建立在栈上

将 operator new() 设置为私有。原因:当对象建立在堆上时,是采用 new 的方式进行建立,其底层会调用 operator new() 函数,因此只要对该函数加以限制,就能够防止对象建立在堆上。

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

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

相关文章

Git基础——基本的 Git本地操作

本文涵盖了你在使用Git的绝大多数时间里会用到的所有基础命令。学完之后&#xff0c;你应该能够配置并初始化Git仓库、开始或停止跟踪文件、暂存或者提交更改。我们也会讲授如何让Git忽略某些文件和文件模式&#xff0c;如何简单快速地撤销错误操作&#xff0c;如何浏览项目版本…

辛苦拍摄的视频画面有多个杂物,教你一分钟快速去除

短视频在我们生活中已经成为了人们记录生活、分享生活的重要方式之一。然而&#xff0c;在我们辛苦拍摄的同时难免也会遇到拍摄画面中出现杂物、多余的物体或者是不相干的对象的问题。想要无痕去除的话&#xff0c;随着人工智能的快速发展&#xff0c;AI智能抠像技术为解决这一…

基于spring boot校园疫情信息管理系统/疫情管理系统

摘要 随着计算机技术&#xff0c;网络技术的迅猛发展&#xff0c;Internet 的不断普及&#xff0c;网络在各个领域里发挥了越来越重要的作用。特别是随着近年人民生活水平不断提高&#xff0c;校园疫情信息管理系统给学校带来了更大的帮助。 由于当前疫情防控形势复杂&#xff…

git的使用

1、代码托管平台&#xff1a;github、 coding 、 gitee 2、gitee&#xff08;码云&#xff09;怎么创建创建仓库&#xff08;项目&#xff09;&#xff1a; &#xff08;1&#xff09;、点击 “” ----》新建仓库 &#xff08;2&#xff09;、创建 3、安装git>一直next 4、…

报名倒计时!| 基于RflySim平台飞控底层算法开发专题培训(第二期)

RflySim 暑期学校 飞思实验室“基于RflySim平台飞控底层算法开发”系列专题培训第二期开启报名了&#xff01;专题培训由戴训华副教授以及飞思实验室学生&工程师团队主讲&#xff0c;采用“线上线下”集中授课形式&#xff0c;培训时间为8月28日-9月3日&#xff1b;课程内…

STL——map和set

一、set的介绍 1、set是按照一定次序存储元素的容器&#xff1b; 2、在set中&#xff0c;元素的value也标识它(value就是key&#xff0c;类型为T)&#xff0c;并且每个value必须是唯一的&#xff0c;set中的元素不能在容器中修改(元素总是const)&#xff0c;但是可以从容器中插…

燃气管网监测系统,24小时守护燃气安全

随着社会的发展和人民生活水平的提高&#xff0c;燃气逐渐成为人们日常生活和工作中不可或缺的一部分。然而&#xff0c;近年来&#xff0c;屡屡发生的燃气爆炸问题&#xff0c;也让人们不禁对燃气的安全性产生了担忧。因此&#xff0c;建立一个高效、实时、准确的燃气管网监测…

开始MySQL探索——数据库概述

计算机语言 计算机语言概述 计算机语言&#xff08;Computer Language&#xff09;可以简单的理解为一种计算机和人都能识别的语言。 机器语言 汇编语言 高级语言 机器语言 汇编语言 高级语言 SQL语言基础 SQL的概述 SQL全称&#xff1a;Structured Query Language&…

ubuntu18.04复现yolo v8环境配置之CUDA与pytorch版本问题以及多CUDA版本安装及切换

最近在复现yolo v8的程序&#xff0c;特记录一下过程 环境&#xff1a;ubuntu18.04ros melodic 小知识&#xff1a;GPU并行计算能力高于CPU—B站UP主说的 Ubuntu可以安装多个版本的CUDA。如果某个程序的Pyorch需要不同版本的CUDA&#xff0c;不必删除之前的CUDA&#xff0c;…

2022年30m全国逐年土地覆被数据

1.研究背景 2023年8月,武汉大学杨杰和黄昕教授团队向公众更新发布了CLCD 2022年全国土地覆数据(V1.0.2)。而CLCD 2021年全国土地覆数据(V1.0.1)也是在去年8月向公众更新发布。 中国在过去几十年中经济和人口迅速发展,土地覆盖随之发生巨大变化,因此迫切需要对其进行连续…

【启明智显分享】原来洗衣机还可以如此进行产品升级!

洗衣机是我们日常生活中必不可少的家电之一&#xff0c;而随着科技的不断进步&#xff0c;洗衣机也在不断进行创新和升级&#xff0c;以提供更好的用户体验和功能。 而今天&#xff0c;我们要介绍的就是一种创新的洗衣机方案&#xff0c;即将3.5寸串口屏应用于洗衣机中&#x…

Java算法_ BST 中第 k 个最小元素 (LeetCode_Hot100)

题目描述&#xff1a;给定一个二叉搜索树的根节点 &#xff0c;和一个整数 &#xff0c;请你设计一个算法查找其中第 个最小元素&#xff08;从 1 开始计数&#xff09;。 获得更多&#xff1f;算法思路:代码文档&#xff0c;算法解析的私得。 运行效果 完整代码 /*** 2 * Aut…

Hugo托管到Github Pages

Github通过其Github Pages服务可以user、project或organization提供免费快速的静态托管&#xff0c;同时使用Github Actions自动化开发工作流和构建。 1.创建Github仓库 可见性为public。 命名为username.github.io&#xff0c;username为你的Github用户名。 2.添加远程仓库…

[docker][WARNING]: Empty continuation line found in:

报警内容&#xff1a; 下面展示一些 内联代码片。 //执行 sudo docker build ubuntu:v1.00 . [WARNING]: Empty continuation line found in:出现上述错误原因为18行多了一个 " \" 符号&#xff0c;去除即可

cuda面试准备(一),架构调试

1 cuda架构 硬件方面 SP (streaming Process) ,SM (streaming multiprocessor) 是硬件(GPUhardware) 概念。而thread,block,grid,warp是软件上的(CUDA) 概念 SP:最基本的处理单元,streaming processor,也称为CUDA core,最后具体的指令和任务都是在SP上处理的。GPU进行并行…

Unity 之`Physics.Raycast()`方法,射线检测

文章目录 总述参数解释形参前两个变量可以用Ray 来代替 返回值 总述 当你在Unity中使用Physics.Raycast()方法时&#xff0c;你实际上是在进行一种射线检测&#xff0c;以查看一条射线是否与场景中的碰撞体相交。这可以用来实现很多不同的功能&#xff0c;如点击选择物体、射击…

【Flutter】Flutter 使用 device_info_plus 获取设备的制造商、型号等信息

【Flutter】Flutter 使用 device_info_plus 获取设备的制造商、型号等信息 文章目录 一、前言二、安装和基本使用三、实际业务中的用法四、完整示例五、总结 一、前言 在这篇博客中&#xff0c;我将为你介绍一个非常实用的 Flutter 插件&#xff1a;device_info_plus。这个插件…

ESP32应用教程(1)— VL53L3CX距离传感器

文章目录 前言 1 产品概述 1.1 技术规格 1.2 系统框图 1.3 设备引脚分布 2 工作流程 2.1 系统功能描述 2.2 状态机描述 2.3 测距模式说明 3 控制接口 3.1 设备地址 3.2 IC写1个字节数据 3.3 IC读1个字节数据 3.4 IC写多个字节数据 3.5 IC读多个字节数据 3.6 IC…

本地编译angular提示内存溢出

本地遇到编译angular时&#xff0c;报如下错误&#xff1a; FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory 两种解决办法&#xff0c;具体如下&#xff1a; 设置环境变量&#xff0c;见图&#xff1a; 直接在…

Wireshark数据抓包分析之互联网控制报文协议_ICMP

一、实验目的: 通过使用wireshark抓取的ICMP数据包对这个ICMP控制报文进行分析 二、预备知识&#xff1a; 1.ICMP协议概述&#xff1a;ICMP是Internet Control Message Protocol的缩写&#xff0c;即互联网控制报文协议。它是TCP/IP协议族的一个子协议&#xff0c;用于IP主机、…