C++内存管理

2023-09-13 18:01:25

目录

一.new和delete

二.operator new与operator delete函数

三.new和delete的实现原理

四.在VS2022编译器下new和delete不匹配的问题

五.定位new

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

七.C++内存分布 

八.内存泄漏


C++在内存管理上引入了两个操作符,分别是new,delete,和malloc/free有许多不同,比如后者是函数前者是操作符,但new/delete又是基于malloc/free实现的。

一.new和delete

        C++的内存管理和C有不同,C使用malloc/calloc/realloc申请空间,free释放空间。C++是使用new和delete来申请和释放空间。下面是new和delete的用法:

#include<iostream>
using namespace std;

int main()
{
	// 动态申请一个int类型的空间
	int* ptr1 = new int;

	// 动态申请一个int类型的空间并初始化为10
	int* ptr2 = new int(10);

	// 动态申请10个int类型的空间
	int* ptr3 = new int[3];

	//动态申请10个int类型空间并初始化
	int* ptr4 = new int[3] {};//里面按顺序填写初始化的值,没有就是0

	delete ptr1;
	delete ptr2;
	delete[] ptr3;
	delete[] ptr4;
	return 0;
}

注意:在申请自定义类型空间时new和delete会自动调用构造和析构函数,new申请空间失败会抛异常! 这些都和C的内存函数不同。而且new[]和delete[]要匹配使用!

二.operator new与operator delete函数

       operator new和operator delete是两个系统提供的全局函数。

        在使用new时new在底层会调用operator new函数,本质上使用的还是malloc函数来开辟空间,还会判断malloc是否开辟成功(不像C需要用户自己判断),成功就返回,失败就会执行用户提供的空间不足应对措施,如果用户没有提供就抛异常(C++11新特性)。

        使用delete时delete会在底层调用operator delete函数,本质上是使用free来释放空间

        注意:operator new/delete对于自定义类型是不会自动调用构造析构的,只有使用new/delete才会!

//库里面operator new的实现代码
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的实现代码
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)

三.new和delete的实现原理

        对于内置类型new,delete和malloc/calloc/realloc,free没什么区别,申请空间失败new会抛异常,malloc会返回空指针。new/delete是申请/释放单个元素空间,new[]/delete[]申请/释放的是连续空间。

        对于自定义类型malloc/calloc/realloc,free和内置类型完全一样,new/delete分情况讨论:

1.new原理

        (1). 调用operator new函数申请空间

        (2). 在申请的空间上执行构造函数,完成对象的构造
2.delete原理
        ( 1). 在空间上执行析构函数,完成对象中资源的清理工作
        (2).  调用 operator delete 函数释放对象的空间
3.new[]原理
        (1). 调用 operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成 N 个对
象空间的申请
        (2). 在申请的空间上执行 N 次构造函数
4.delete[]原理
        (1). 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理
        (2). 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释
放空间
        
        

四.在VS2022编译器下new和delete不匹配的问题

        下面是两段代码,都是new和delete不匹配的问题,但是一段没报错一段报错。

#include<iostream>
using namespace std;

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
private:
	int a = 1;
};

int main()
{
	A* ptr = new A[10];
	delete ptr;//这里delete和new[]不匹配但没报错
	return 0;
}

下面会报错: 

#include<iostream>
using namespace std;

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()//显示写析构函数
	{}
private:
	int a = 1;
};

int main()
{
	A* ptr = new A[10];
	delete ptr;//这里delete和new[]不匹配但是显示写析构函数却报错了
	return 0;
}

上面的代码只差了一个析构函数就报错了。

        原因是new[]的时候不仅开辟用户需要的空间,还会再开辟的空间前四字节额外开辟一小段空间,里面存放new[]里面的数字。作用是delete[]时告诉delete[]要调用析构函数几次。然后再释放空间,这里还会把前面额外开辟的4字节空间也释放掉。

        但如果是直接用delete释放空间,那么就不会释放前面额外开辟的4字节空间就会报错(代码中ptr指针指向的地址是4字节之后的位置,所以就会导致直接释放空间会漏掉这4字节)。

        上面的情况是有显示写析构函数,如果没有写析构函数编译器会优化,认为默认生成的析构函数没啥作用就在new[]的时候不开辟额外的4字节空间,所以直接用delete释放ptr指向的空间就不会报错。

五.定位new

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

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

使用场景:

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

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

// 定位new/replacement new
int main()
{
	// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A;// 注意:如果A类的构造函数有参数时,此处需要传参
	p1->~A();
	free(p1);

	A* p2 = (A*)operator new(sizeof(A));//这里也只是开空间,执行构造函数
	new(p2)A(10);
	p2->~A();
	operator delete(p2);
	return 0;
}

六.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 在释放空间前会调用析构函数完成空间中资源的清理

七.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 . 代码段 ( 常量区 )
           globalVar在哪里? ____   staticGlobalVar 在哪里?____
           staticVar在哪里? ____   localVar 在哪里?____
           num1 在哪里?____
          char2在哪里? ____   * char2 在哪里?___
          pChar3在哪里? ____       * pChar3 在哪里?____
           ptr1在哪里? ____         * ptr1 在哪里?____
答案从左到右从上到下分别是:C  C  C  A  A  A  A  A  D  A  B

填空题:

          ​​​​​​​ sizeof ( num1 ) = ____ ;
          sizeof ( char2 ) = ____ ;         strlen ( char2 ) = ____ ;
          sizeof ( pChar3 ) = ____ ;     strlen ( pChar3 ) = ____ ;
           sizeof ( ptr1 ) = ____ ;
答案从左到右从上到下分别是:40  5  4  4/8(取决于32位还是64位机器)  4  4/8

上图是C++内存分布,下面是说明:

1. 又叫堆栈, 非静态局部变量 / 函数参数 / 返回值等等,栈是向下增长的。
2. 内存映射段 是高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
创建共享共享内存,做进程间通信。
3. 用于程序运行时动态内存分配,堆是可以上增长的。
4. 数据段 -- 存储全局数据和静态数据。
5. 代码段 -- 可执行的代码 / 只读常量

八.内存泄漏

        什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费,比如new了之后的空间没有delete。
        内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
        服务器需要长期运行,如果有内存泄漏就算是一点点,长期运行下来也会积少成多,然后导致服务器卡死或者重启,损失都是很大的。
        内存泄漏分类:
        堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过 malloc / calloc / realloc / new 等从堆中分配的一
块内存,用完后必须通过调用相应的 free 或者 delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生 Heap Leak
        系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

        如何检测内存泄漏:

        VS可以在main函数之后加上windows系统提供的_CrtDumpMemoryLeaks()函数进行检测,但是只能返回大概泄漏的多少字节,没有准确的位置信息。

        避免内存泄露就应该在平时养成良好的编程习惯,申请的空间记得释放回去。

更多推荐

微服务08-认识和使用SpringAMQP

1.AMQP的认识1.1介绍AMQP是什么?看完你就知道了_hello_读书就是赚钱的博客-CSDN博客_amqp好处:什么connection:消息队列的连接、channel:服务发送接收消息的通道、Queue:消息队列——>这些你都不需要自己编写工作过程:发布者(Publisher)发布消息(Message),经由

常见的查找算法以及分块搜索算法的简明教程

顺序查找最基本的查找算法举例//顺序查找publicstaticintsearchSequence(int[]arr,inttarget){inti=0;for(intarr2:arr){if(arr2==target){returni;}i++;}return-1;}二分查找[!warning]值得注意的是这个二分查

Go的性能优化建议

前言:\textcolor{Green}{前言:}前言:💞这个专栏就专门来记录一下寒假参加的第五期字节跳动训练营💞从这个专栏里面可以迅速获得Go的知识Go的性能优化建议3性能优化建议3.1性能优化建议-Benchmark3.2性能优化建议-slice3.3性能优化建议-Map3.4性能优化建议-字符串处理3.5性能

【AI视野·今日CV 计算机视觉论文速览 第248期】Mon, 18 Sep 2023

AI视野·今日CS.CV计算机视觉论文速览Mon,18Sep2023Totally83papers👉上期速览✈更多精彩请移步主页Interesting:📚Robuste-NeRF,处理高速且大噪声事件相机流的NERF模型。(fromNUS新加坡国立)稀疏噪声事件与稠密事件数据的区别:模型架构:项目网站:https:

Python网络编程(socket)

网络编程指的是:在程序中实现两台计算机之间的通信。Python提供了大量网络编程的工具和库,本文重点学习socket和select模块。网络编程涉及许多关于TCPIP的基础知识,本文默认对这些知识已经了解了,不再对TCPIP相关的知识进行学习。socket模块这个模块提供了访问BSD套接字的接口。在所有现代Unix系统

开始在 Windows 上将 Python 用于 Web 开发

🎬岸边的风:个人主页🔥个人专栏:《VUE》《javaScript》⛺️生活的理想,就是为了理想的生活!目录设置开发环境安装适用于Linux的Windows子系统设置VisualStudioCode创建新项目安装Python、pip和venv创建虚拟环境打开WSL-Remote窗口安装MicrosoftPython扩

【C# 基础精讲】抽象类与接口

抽象类(AbstractClass)和接口(Interface)是面向对象编程中两种重要的概念,它们用于定义类的结构、行为和关系,是实现多态性、代码复用和系统设计的关键手段。在C#及其他面向对象编程语言中,抽象类和接口都发挥着重要作用。本文将详细解释抽象类和接口的概念、特点、用法以及在C#中的应用。1.抽象类的概念与特

同城信息服务源码 本地生活服务小程序源码

同城信息服务源码本地生活服务小程序源码功能介绍:基本设置:网站参数、安全设置、分站管理、支付设置、操作日志、地区设置、公交地铁、国际区号、清理缓存、模板风格、模块管理、域名管理、底部菜单、消息通知、登录设置其他设置:关键词管理、单页文档、网站公告、帮助信息、网站协议、广告设置、友情链接、举报管理、意见反馈、投诉列表短信

CH573-09-BLE蓝牙安卓应用二次开发——RISC-V内核BLE MCU快速开发教程

一、基础工程搭建在上一章最后一讲的BLE蓝牙例程中,我们使用了沁恒官方的BLE调试助手完成数据发送,接下来我们使用AndroidStudio完成一款简易的BLE调试助手。1、参考文章我这里参考了CSDN中的一位博主“摸爬滚打的程序媛”的文章以及对应文章中的AndroidStudioBLE应用工程的Demo。版权声明:链

MP3算法及代码例程

MP3(MPEG-1AudioLayerIII)是一种数字音频压缩算法,用于对音频进行高效的压缩。MP3算法能够显著减小音频文件的大小,同时保持较高的音质。以下是MP3算法的主要步骤:采样率转换:将输入音频信号的采样率转换为固定的值,通常为44.1kHz。这是因为人耳对于音频的感知范围大约在20Hz到20kHz之间,因

9.3.5网络原理(应用层HTTP/HTTPS)

一.HTTP:1.HTTP是超文本传输协议,除了传输字符串,还可以传输图片,字体,视频,音频.2.3.HTTP协议报文格式:a.首行,b.请求头(header),c.空行(相当于一个分隔符,分隔了header和body),d.正文(body).4.5.URL:唯一资源描述符(长度不限制).a.b.注意:查询字符串(qu

热文推荐