【C/C++内存管理】——我与C++的不解之缘(六)

2024-09-07 15:44
文章标签 c++ 内存 管理 不解之缘

本文主要是介绍【C/C++内存管理】——我与C++的不解之缘(六),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

        最近开学了,更新有些迟缓了;

现在来学C/C++中的内存管理

一、C/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);
}

对于以上代码,这些创建的全局变量,局部变量以及静态变量等都分别存放在内存的哪些区域?

说明:

1、又叫做堆栈 -- 非静态局部变量、函数参数和返回值等,栈是向下增长的。

2、用于程序运行时动态内存分配,堆是向上增长的。

3、数据段(静态区) -- 存储全局数据和静态数据。

4、代码段(常量区) -- 可执行的代码/只读常量(比如,上述的常量字符串"abcd")。

5、内存映射段 是高效的 I/O映射方式,用于装载一个共享的动态内存库,用户可以使用接口创建共享内存,做进程间通信(后面再学习这一块的知识)。

二、C语言中动态内存管理方式

        在C语言中,学习过C语言的动态内存管理方式:malloc /calloc /realloc /free

这里就不过多的讲解,如有遗忘就去重温一下C语言——动态内存管理

三、C++中内存管理方式

        由于C++是兼容C语言的,所以C语言的内存管理方式在C++当中也可以继续使用;但是,在一些方面,C语言的malloc /calloc /realloc /free使用起来就比较麻烦;

        因此C++提出了自己的内存管理方式:通过 new和 delete 操作符进行动态内存管理

        3.1、new / delete 操作内置类型

void test()
{//动态申请一个int大小的空间int* p1 = new int;//动态申请一个int大小的空间,并初始化为520;int* p2 = new int(520);//动态申请十个int大小的空间(数组)int* p3 = new int[10];//动态申请十个int大小的空间(数组),并初始化int* p4 = new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9};delete p1;delete p2;//释放数组(多个内置类型)delete[] p3;delete[] p4;
}

注意:

        申请和释放单个元素的空间,使用new和delete操作符;申请和释放连续的空间,使用new[ ]和delete[ ]。(配套使用)

        3.2、new和delete操作自定义类型

        对于内置类型,new/delete 和malloc/free 差别不是很大;

        而对于自定义类型,最大的区别就是,new和delete除了会开辟空间还会调用自定义类型的构造函数和析构函数。

class A
{
public:A(int a = 1):_a(a){std::cout << "A()" << std::endl;}~A(){std::cout << "~A()" << std::endl;}
private:int _a;
};int main()
{A* p1 = (A*)malloc(sizeof(A));free(p1);A* p2 = new A;delete p2;return 0;
}

      可以看到,malloc和free并没有调用构造函数和析构函数;

这里,new和delete调用了自定义类型的构造函数和析构函数。

四、operator new与operator delete函数

        4.1、operator new 和operator delete 函数

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

/*
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)

        以上是库里面的源代码,可以大致看出operator new 实际上也是通过malloc来申请空间,如果申请成功,就直接返回;如果失败就执行用户通过的空间不足应对措施,如果用户没有提供就抛异常 operator delete 最终是通过free来释放空间的。

        4.2、抛异常

        这里简单使用一下抛异常和捕获异常(抛异常也是new和malloc的区别之一)

先看一下代码,这里在x86(32位)环境下动态开辟空间,直到申请失败抛异常

#include<iostream>
int main()
{int* p;do{p = new int[1024 * 1024];std::cout << p << std::endl;} while (p);return 0;
}

这里提示未经处理的异常,说明new动态申请空间失败了,抛异常。现在使用try catch捕获异常并输出。

#include<iostream>
#include<exception>
int main()
{try{int* p;do{p = new int[1024 * 1024];std::cout << p << std::endl;} while (p);}catch (const std::exception& e){std::cout << e.what() << std::endl;}return 0;
}

五、new 和delete 的实现原理

        5.1、内置类型

        如果动态申请内置类型的空间,new和malloc、delete和free 基本相似;

不同的是:

1、new/delete申请和释放的是单个元素的空间,new[ ] 和delete[ ] 申请和释放的是连续的空间。

2、new在申请空间失败会抛异常,而malloc会返回NULL。        

        5.2、自定义类型

5.2.1、new的原理

1、先调用operator new 函数申请空间。

2、在申请的空间上调用自定义类型的构造函数,完成对象的构造。

5.2.2、delete的原理

1、在空间上调用自定义类型的析构函数,完成对象中资源的清理

2、调用operator delete 函数释放空间。

5.2.3、new T[N] 的原理

1、调用operator new[ ]函数,在operator中实际调用了operator new 完成N个对象的申请

2、在申请的空间上调用N次构造函数。

5.2.4、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的定义表达式进行显示的调用构造函数进行初始化。

#include<iostream>
class A
{
public:A(int a = 1):_a(a){std::cout << "A()" << std::endl;}~A(){std::cout << "~A()" << std::endl;}
private:int _a;
};
int main()
{//动态申请空间,malloc不会初始化A* p1 = (A*)malloc(sizeof(A));//new表达式,显示调用构造函数new(p1)A(99);//显示调用析构函数p1->~A();//释放空间free(p1);return 0;
}

七、malloc/free 和 new/dalete的区别

        首先: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语言内存管理中,提到过内存泄露,这里简单理解一下,在之后学习中会深入学习内存泄露以及避免内存泄露。

        内存泄露:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

        内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

感谢各位大佬支持并指出问题,

                        如果本篇内容对你有帮助,可以一键三连支持以下,感谢支持!!!

这篇关于【C/C++内存管理】——我与C++的不解之缘(六)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1145474

相关文章

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表

使用C++实现单链表的操作与实践

《使用C++实现单链表的操作与实践》在程序设计中,链表是一种常见的数据结构,特别是在动态数据管理、频繁插入和删除元素的场景中,链表相比于数组,具有更高的灵活性和高效性,尤其是在需要频繁修改数据结构的应... 目录一、单链表的基本概念二、单链表类的设计1. 节点的定义2. 链表的类定义三、单链表的操作实现四、

使用C/C++调用libcurl调试消息的方式

《使用C/C++调用libcurl调试消息的方式》在使用C/C++调用libcurl进行HTTP请求时,有时我们需要查看请求的/应答消息的内容(包括请求头和请求体)以方便调试,libcurl提供了多种... 目录1. libcurl 调试工具简介2. 输出请求消息使用 CURLOPT_VERBOSE使用 C