C++内存分布 new和delete介绍

目录

C/C++内存分布

栈区

堆区

静态区

常量区

C++   new和delete

分配空间形式对比

new  delete与malloc  free的区别

可不可以串着使用new和free呢


C/C++内存分布

C++的内存分布,大体上分为栈区 堆区  静态区 常量区

栈区

栈区是用于存储函数调用时的局部变量 函数参数 返回地址等,当一个函数被调用的时候,栈区会分配一块空间给这个函数,其局部变量会被分配到栈上,函数执行结束后,这些局部变量的内存空间会被释放。栈内存是自动管理的,不需要手动分配或释放。常见的存栈区的有局部变量,函数,函数调用的参数,函数的返回地址以及返回值。请注意编译器产生的临时变量也存在栈区,虽然说它具有常性,但那只是一个权限限制(相当于自带const),它依旧存储在栈区,用完了自己就会销毁。以上的也全适用于main函数。

堆区

堆区是一种动态分配的内存区域,用来存动态分配的对象和数据,直白点讲malloc,calloc,new等动态分配出来的空间都在堆区。堆区最重要的议题就是内存泄漏了,手动申请的堆空间如果不手动free虽然程序结束这段空间会自动,但是资源没回收会发生内存泄漏。

  内存泄漏举个例子,出去旅行在酒店开了房(这个房间就是我malloc申请的空间),但是我行李箱(行李箱就是资源,其实就是这段空间定义的变量之类的东西,这些变量也会使用申请的空间)忘拿走扔在酒店直接退房走了。我退房了我对这个房间就没有使用权了,我的行李箱也许还在原地也许已经被人拿走了。现实生活中肯定会回去找酒店帮忙取出来行李箱,但是对于计算机来说这片空间已经没有使用权了,也就没办法取出行李箱(资源)了,此时行李箱可能会扔在酒店的某个地方(反正没人管了),行李箱也会占用空间的,如果遗留的行李箱(资源)越来越多,终有一天会把整个空间占满,导致下一次手动申请空间空间会不足。

  我只是举个例子帮助理解,现实生活中肯定不会像上面那样。

  总的来说内存泄漏就是指那些已经被分配但未被正确释放(即未处理)的内存资源。这些资源在逻辑上已经不再被程序所使用,但由于没有调用相应的释放函数(如free在C语言中),malloc申请的空间虽然程序结束会自动收回,但是这片空间里的资源(变量什么的)不会清理,它们仍然占据着物理内存空间。由于这些未释放的资源仍然占用空间,如果程序持续运行且不断产生新的内存泄漏,这些闲散资源确实会越积越多。在堆(heap)上分配的内存空间是有限的,因此,如果泄漏的内存量持续增长,最终确实有可能将堆空间占满。当堆空间被占满时,程序将无法再成功分配新的内存块,这可能会导致程序崩溃或表现出不可预测的行为。

      为什么内存泄漏不会报错呢,内存泄漏相当于慢性病,平时可能都看不出来,但不表示没有问题。等变得严重了,身体受不了了才会给你反应,但是到那时候可能就已经晚了

       那么指针悬挂有又是什么呢,指针变量是保存地址的变量,解引用才是访问这片空间。malloc申请空间时一般会使用指针变量来保存这片空间的地址,如果你已经free这块空间,但是指针变量没有手动置为空,那么就产生了指针悬挂。指针悬挂不会直接报错,只是风险特别大,只有使用的时候才会报错。如果你free完空间后指针变量没有置为空,那么接下来不小心又使用了这个指针变量,进行解引用想要访问指针变量保存地址的那块空间时就会程序崩溃。因为空间已经回收了,你还要访问一个你已经没权限访问的空间(这片空间可能已经被系统回收并分配给其他程序或变量使用,或者根本就不再属于程序的可用内存范围),这就意味着已经退了房,但是房卡忘记还了,有一天又偷偷拿房卡回这个房间看看,这样就会发生不可预估的问题。所以已经释放空间的指针变量手动置空比较好,减少风险

静态区

全局变量和静态变量存储在静态存储区中,静态变量在整个程序的整个执行过程都存在,但是静态成员函数不在静态区,静态成员函数和别的成员函数都一样放在公共代码段里,这个代码段是指程序的一部分,包含了程序的所有指令。静态区的东西作用域仅限于定义它们的文件或函数,虽然作用域在函数里,但是这只是说在别的函数里不能使用在这个函数里定义的静态的局部变量,但是它已经存在没有销毁,等程序结束才会销毁。

静态区被称为静态的主要原因是因为它的内存分配和释放是在程序运行期间静态确定的,即在程序启动时就会被分配内存,在程序结束时才会被释放。静态区中的数据在程序整个执行过程中保持不变,不会随着函数的调用或代码块的结束而被销毁,因此被称为静态。

加static的变量基本都是存静态区的,全局变量也在这里,全局的指针变量也在静态区。

常量区

常量数据(如字符串常量)存储在常量存储区中。这些数据在程序运行期间保持不变,通常是只读的。常量存储区的数据在程序启动时被加载,直到程序结束时才被释放。

来看一道题目

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);
}
1. 选择题:
选项 : A . B . C . 数据段 ( 静态区 ) D . 代码段 ( 常量区 )
globalVar 在哪里? ____ staticGlobalVar 在哪里? ____
staticVar 在哪里? ____ localVar 在哪里? ____
num1 在哪里? ____
char2 在哪里? ____ * char2 在哪里? ___
pChar3 在哪里? ____ * pChar3 在哪里? ____
ptr1 在哪里? ____ * ptr1 在哪里? ____
2. 填空题:
sizeof ( num1 ) = ___ ;
sizeof ( char2 ) = ____ ; strlen ( char2 ) = ____ ;
sizeof ( pChar3 ) = ____ ; strlen ( pChar3 ) = ____ ;
sizeof ( ptr1 ) = ____ ;
3. sizeof strlen 区别?

globalVar全局变量在静态区,常量才放常量区。 staticGlobalVar用static修饰,全局的静态变量放在静态区。静态区不只放静态变量还放全局变量

staticVar局部静态变量依旧在静态区。localVar局部变量放到栈里面。char2在栈区,虽然它存字符串常量,但是是在栈上为数组 char2 分配了足够的空间来存储字符串 "abcd" 的拷贝。编译器会将字面量字符串 "abcd" 的内容复制到 char2 数组中。这时,char2 数组包含了与字面量字符串相同的内容,但它存储在栈上,而不是常量区。

*char2是数组第一个元素,也就是a,因为此时是相当于把字符串拷贝过来存到数组里,数组在栈上开了空间存这些东西。即使他是常量此时被拷贝过来也是在栈上,受装它的容器限制。但是因为是字符串常性不能修改还是要保证的,所以加const

pChar3在栈区,指针变量本身在栈区,指针变量用来保存地址的,但是它本身也需要空间去存。此时定义在局部函数里(main也算),所以在栈区

*pChar3在常量区,与char2不同的是这是指针直接指向常量区,pChar3保存的是常量区存字符串本体的空间地址,所以*pChar3在常量区

ptr1定义在栈区,所以在栈区,保存的地址是堆区空间的地址,所以*ptr1在堆区

sizeof(num1) 存的是数组,sizeof计算类型或对象在内存中的大小(以字节为单位),这是数组,1个int数占4个字节,这个数组总共10个数(不明显写的自动填充0),所以sizeof(num1)是40

sizeof(char2sizeof计算类型或对象在内存中的大小(以字节为单位),字符串会在末尾存一个\0作为结束标志,所以sizeof(char2)是5。 

strlen(char2)strlen只会算字符串有效字符,\0不会计算进去,所以结果是4

sizeof(pChar3) pChar3是指针,指针在32位下sizeof是4,64位下是8

strlen(pChar3)  strlen只会算字符串有效字符,\0不会计算进去,所以结果是4

sizeof(ptr1)  是个指针,同pChar3

C++   new和delete

new和delete是C++进行动态管理内存的方式,是C语言里malloc等和free的升级版,malloc他们俩能干到的事,new他们都能干到。

分配空间形式对比

单个类型分配空间malloc和free形式

int main()
{
	int* arr = (int*)malloc(sizeof(int));
	int* brr = (int*)malloc(sizeof(int)*10);//分配多个int类型的空间
	if (arr == NULL)
	{
		perror("malloc fail");//内存分配失败打印
		exit(1);//退出
	}
	if (brr == NULL)
	{
		perror("malloc fail");//内存分配失败打印
		exit(1);//退出
	}
	free(arr);//释放空间
	arr = NULL;//手动置空,防止悬挂指针
    brr=NULL;
}

new和delete形式

int main()
{
	int* arr = new int;
	int* brr = new int[10];//分配多个类型的空间
	delete arr;
	delete[] brr;
	arr = NULL;
	brr = NULL;
}

new  delete与malloc  free的区别

1.对于自定义类型new会自动调它的构造函数,delete会自动析构函数,而malloc和free不会

而内置类型基本是一样的

malloc的情况,不会调用构造函数

 new和delete的情况

new A[3]相当于构造了三次对象,相当于对象数组,所以可以通过数组的方式来访问他们,brr[0],brr[1],brr[2]

析构这个对象数组不是用delete brr[],这样会报错的,而是delete [] brr;

2.malloc分配空间返回空所以一定要判空,new会抛异常需要捕获异常

在C语言中,malloc函数用于动态内存分配,它返回一个指向所分配内存的指针,如果内存分配失败,则返回NULL。由于C语言不支持异常处理机制,因此malloc需要程序员显式地检查其返回值是否为NULL,以确定内存是否成功分配。这是C语言处理错误的一种常见方式——通过返回值或特殊错误码来指示函数调用的成功或失败。

而在C++中,new操作符被设计为在内存分配失败时自动抛出std::bad_alloc异常。C++的异常处理机制允许程序在运行时遇到错误情况时,通过抛出和捕获异常来优雅地处理这些错误。因此,当new无法分配所需内存时,它会自动触发异常处理流程,而不需要程序员显式地检查返回值。

区别在于C语言返回的是错误码,而且你用malloc每次开一次空间就要写一次判断错误码是什么,然后要手动进行退出。而C++是自动抛出异常,他们会自动退出。

如果你捕获了std::bad_alloc异常,你可以选择在捕获块中打印任何你想要的信息。通常,你可能会打印一条错误消息,告知用户或开发者内存分配失败的情况。例如:

try {  
    int* largeArray = new int[SOME_LARGE_NUMBER];  
    // ... 使用largeArray ...  
    delete[] largeArray;  
} catch (const std::bad_alloc& e) {  
    std::cerr << "内存分配失败: " << e.what() << std::endl;  
    // 可以在这里添加其他错误处理代码  
}

那么new究竟是怎么来实现开空间的呢,可以通过反汇编来看一下

call是调函数的意思,通过反汇编可以看出来new其实调了一个operator new的函数(这个不是重载),operator new函数内容如下,可以看出operator new实际上底层依旧是malloc,也需要类型大小,也需要强转类型,但是多了抛异常

 其实operator new因为底层是malloc,而operator delete 是free,所以他们其实也可以直接使用

定位new表达式(placement-new)

但是 为什么不会打印构造函数和析构函数字样呢,因为operator new他们也不会走构造和析构,但是new会。总结一下malloc的功能其实就只是开空间而已,而operator new不只可以开空间,而且可以抛异常,而new不只能通过operator new来开空间和抛异常,而且可以完成析构。那么究竟在operator new上面加了什么以用来调构造函数呢。直接通过开辟空间地址的指针arr->A去访问是不行,可以试一下但是基本都是报错。

这就引出了定位new表达式,定位new表达式是在已分配的原始内存空间中调用构造函数的表达式

使用格式:
new (place_address) type 或者 new (place_address) type(initializer-list)
place_address 必须是一个指针, initializer-list 是类型的初始化列表
使用场景:
定位 new 表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义
类型的对象,需要使用 new 的定义表达式进行显示调构造函数进行初始化。

多重对象数组开空间构造定位new表达式是通过偏移量来访问各个对象空间来进行构造和析构的

 为什么构造不能用arr->A(1),而析构可以直接这样呢,虽然可能很多人会说这是规定,然后就不了了之,我还是想说得清楚些

在C++中,构造函数和析构函数的使用方式确实有所不同,这主要源于它们各自的目的和生命周期管理的差异。

构造函数用于初始化对象,它在创建对象时自动调用。当你使用new关键字创建一个对象时,C++会首先分配内存,然后调用对象的构造函数来初始化这块内存。构造函数没有返回值(也不是void),因为它们负责构建对象本身,而不是执行某个操作并返回结果。因此,你不能直接通过指针来调用构造函数,因为构造函数的调用是与对象的创建紧密绑定的。

相比之下,析构函数用于销毁对象并清理资源。当对象的生命周期结束时(例如,当对象离开其作用域或被delete释放时),析构函数会自动调用。你可以通过指针显式地调用析构函数,因为这时你已经有了一个存在的对象,你只是想提前结束它的生命周期。通过指针调用析构函数的形式是ptr->~TypeName(),其中ptr是指向对象的指针,TypeName是对象的类型。这种显式调用通常与定位new(placement new)一起使用,当你手动管理对象的内存时,需要显式地调用析构函数来执行清理操作。

可不可以串着使用new和free呢

对于内置类型来说new和free是可以串着用的,但是不推荐串着用,因为有可能发生奇怪的问题

对于 内置类型是不行的,一方面malloc和free调不了构造函数和析构函数,一方面在于free可能会极大可能造成内存泄漏

为什么呢,对于delete来说分为两个步骤,operator delete这个底层是free,另一方面它还会调析构

 如果类里面的成员变量是别的类类型并且涉及到要另外malloc开空间的话,调这个类的析构函数函数的同时会自动调这个类类型成员的析构函数把它的空间资源先析构了,然后是析构类。如果是free会直接把类的空间释放了,但是这个类也可能有类成员开了空间啊,free不会考虑这个,这就造成了内存泄漏。

比如说类与对象里面写的函数两个别的类类型stack的类queue

总结一下

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言中,如何判断两个数组是否包含相同元素?

在C语言中判断两个数组是否包含相同元素可以采用多种方法&#xff0c;其中最常见的方法是使用排序和比较两个数组的元素。在解释这个问题之前&#xff0c;我们需要了解一下C语言中的数组、排序算法和比较方法。 数组 数组是C语言中一种基本的数据结构&#xff0c;它是一系列相…

mysql的DDL语言和DML语言

DDL语言&#xff1a; 操作数据库&#xff0c;表等&#xff08;创建&#xff0c;删除&#xff0c;修改&#xff09;&#xff1b; 操作数据库 1&#xff1a;查询 show databases 2:创建 创建数据库 create database 数据库名称 创建数据库&#xff0c;如果不存在就创建 crea…

Linux论坛搭建

1.安装httpd服务 1.1安装httpd软件 [rootlocalhost yum.repos.d]# dnf install httpd 1.2.修改httpd的配置 [rootlocalhost yum.repos.d]# vim /etc/httpd/conf/httpd.conf 1.3.启动这个httpd服务,并查看它的状态 [rootlocalhost yum.repos.d]# systemctl start httpd [ro…

前端调用DRI后端API出现跨域资源共享(CORS)问题解决办法

目录 1. 引言2. 跨源资源共享和实现方法3. 在Django项目中配置django-cors-headers库Reference 1. 引言 在进行后端API开发时&#xff0c;有时会遇到“跨域资源共享 (CORS) 请求…被阻止“的错误&#xff0c;如图1所示。本文讲解如何在使用DRF&#xff08;Django REST Framewo…

什么是ISP,为什么跨境推荐ISP?

ISP&#xff0c;全称Internet Service Provider&#xff0c;即“互联网服务提供商”。它是为个人或企业提供访问、使用或参与互联网服务的组织&#xff0c;主要为用户提供互联网接入业务、信息业务和增值业务。ISP是经国家主管部门批准的正式运营企业&#xff0c;享受国家法律保…

多模光纤标准:OM1、OM2、OM3、OM4和OM5

【摘要】 在当今信息时代&#xff0c;光纤通信作为一种高速、高带宽的数据传输方式&#xff0c;已经成为现代通信网络的重要基石。而在光纤通信系统中&#xff0c;多模光纤因其适用于短距离传输和相对低成本而备受青睐。本文瑞哥将带大家好好了解多模光纤中的不同标准&#xff…

Error: contextBridge API can only be used when contextIsolation is enabled

在electron项目中preload.js文件使用下面的方法时报错 const { contextBridge, ipcRenderer } require(electron); contextBridge.exposeInMainWorld(electronApi, {});node:electron/js2c/renderer_init:2 Unable to load preload script: D:\Vue\wnpm\electron\preload.js …

大型企业高效内部协同,向日葵SDK私有化部署案例解析

大型集团企业的内部&#xff0c;沟通协作的重要性不言而喻&#xff0c;我们时常能听到关于所谓“大企业病”的吐槽&#xff0c;多数也是源于企业内部沟通协作效率低&#xff0c;进而导致内耗加重。甚至我们可以这么说&#xff0c;越是发展壮大的集团企业&#xff0c;其内部的沟…

java:Http协议和Tomcat

HTTP协议 Hyper Text Transfer Protocol 超文本传输协议,规定了浏览器和服务器之间数据传输的规则 特点: 基于TCP协议,面向连接,安全 基于请求响应模型:一次请求对应一次响应 HTTP协议是无状态协议,对事务的处理没有记忆能力,每次请求-响应都是独立的. 优点 速度较快 …

图论基础知识 深度优先(Depth First Search, 简称DFS),广度优先(Breathe First Search, 简称DFS)

图论基础知识 学习记录自代码随想录 dfs 与 bfs 区别 dfs是沿着一个方向去搜&#xff0c;不到黄河不回头&#xff0c;直到搜不下去了&#xff0c;再换方向&#xff08;换方向的过程就涉及到了回溯&#xff09;。 bfs是先把本节点所连接的所有节点遍历一遍&#xff0c;走到下…

平安城市 停车场 景区对讲SV-6201-T IP网络非可视对讲报警柱简介

平安城市 停车场 景区对讲SV-6201-T IP网络非可视对讲报警柱简介 18123651365微信 功能特点&#xff1a; 全金属外壳&#xff0c;户外防风雨&#xff0c;坚固耐用&#xff0c;易于识别单键呼叫&#xff0c;可通过软件指定呼叫目标&#xff0c;双向对讲广播喊话终端内置扬声器…

LabVIEW连接PostgreSql

一、安装ODBC 下载对应postgreSQL版本的ODBC 下载网址&#xff1a;http://ftp.postgresql.org/pub/odbc/versions/msi/ 下载好后默认安装就行&#xff0c;这样在ODBC数据源中才能找到。 二、配置系统DSN 实现要新建好要用的数据库&#xff0c;这里的用户名&#xff1a;postg…

SpringCloud 之 服务消费者

前提 便于理解我修改了本地域名》这里!!! 127.0.0.1 eureka7001.com 127.0.0.1 eureka7002.com 127.0.0.1 eureka7003.comRest学习实例之消费者 创建一个消费者去消费 消费者模块展示 1、导入依赖 <!-- 实体类api自己创建的模块 Web 部分依赖展示--><depe…

57-VPX电路设计

视频链接 VPX连接器电路设计01_哔哩哔哩_bilibili VPX接口电路设计 1、VPX介绍 1.1、VPX简介 VPX总线是VITA(VME International Trade Association, VME国际贸易协会)组织于2007年在其VME总线基础上提出的新一代高速串行总线标准。VPX总线的基本规范、机械结构和总线信号等…

Golang特殊init函数

介绍 init()函数是一个特殊的函数&#xff0c;存在一下特性 不能被其它函数调用&#xff0c;而是子main()函数之前自动调用不能作为参数传入不能有传入参数和返回值 作用&#xff1a; 对变量进行初始化检查/修复程序状态注册运行一次计算 以下是<<the way to go>>…

(开源版)企业AI名片S2B2C商城系统商业计划书

团队使命 擎动人工智能跃迁&#xff0c;融技术与商业之行 项目背景 话说2022年12月7日那天&#xff0c;国务院大大发布了个重磅消息&#xff0c;宣布咱们国家的三年抗疫大战终于告一段落&#xff0c;全面放开啦&#xff01;这意味着咱们的市场经济要重新焕发生机啦&#xff…

【EI会议|稳定检索】2024年航空航天、空气动力学与自动化工程国际会议(ICAAAE 2024)

2024 International Conference on Aerospace, Aerodynamics, and Automation Engineering 一、大会信息 会议名称&#xff1a;2024年航空航天、空气动力学与自动化工程国际会议 会议简称&#xff1a;ICAAAE 2024 收录检索&#xff1a;提交Ei Compendex,CPCI,CNKI,Google Schol…

短袖面料有哪些种类?质量好的短袖品牌实测推荐

夏天确实是一个让人感到炎热的季节&#xff0c;而选择合适的短袖对于提高穿着的舒适度和避免不必要的困扰非常重要。所以为了让大家能够选到一些合适自己而且质量好短袖&#xff0c;今天为大家总结了几点关键的选购技巧或一些知识科普&#xff01;并且结合我近期测评过的众多短…

【C++ STL序列容器】list 双向链表

文章目录 【 1. 基本原理 】【 2. list 的创建 】2.1 创建1个空的 list2.2 创建一个包含 n 个元素的 list&#xff08;默认值&#xff09;2.3 创建一个包含 n 个元素的 list&#xff08;赋初值&#xff09;2.4 通过1个 list 初始化另一个 list2.5 拷贝其他类型容器的指定元素创…

Qt : 在QTreeWidget中添加自定义右键菜单

一、引言 如图&#xff0c;我们需要在一个QTreeWidget 控件中添加了自定义右键菜单。 二、思路 如何做到的呢&#xff0c;很简单。浅浅记录和分享一下。 继承QTreeWidget&#xff0c;定义一个子类CustomTreeWidget &#xff0c;在重写contextMenuEvent 事件即可。 三、代…