【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++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

Python内存管理机制之垃圾回收与引用计数操作全过程

《Python内存管理机制之垃圾回收与引用计数操作全过程》SQLAlchemy是Python中最流行的ORM(对象关系映射)框架之一,它提供了高效且灵活的数据库操作方式,本文将介绍如何使用SQLAlc... 目录安装核心概念连接数据库定义数据模型创建数据库表基本CRUD操作创建数据读取数据更新数据删除数据查

在Node.js中使用.env文件管理环境变量的全过程

《在Node.js中使用.env文件管理环境变量的全过程》Node.js应用程序通常依赖于环境变量来管理敏感信息或配置设置,.env文件已经成为一种流行的本地管理这些变量的方法,本文将探讨.env文件... 目录引言为什么使php用 .env 文件 ?如何在 Node.js 中使用 .env 文件最佳实践引

C++读写word文档(.docx)DuckX库的使用详解

《C++读写word文档(.docx)DuckX库的使用详解》DuckX是C++库,用于创建/编辑.docx文件,支持读取文档、添加段落/片段、编辑表格,解决中文乱码需更改编码方案,进阶功能含文本替换... 目录一、基本用法1. 读取文档3. 添加段落4. 添加片段3. 编辑表格二、进阶用法1. 文本替换2

C++中处理文本数据char与string的终极对比指南

《C++中处理文本数据char与string的终极对比指南》在C++编程中char和string是两种用于处理字符数据的类型,但它们在使用方式和功能上有显著的不同,:本文主要介绍C++中处理文本数... 目录1. 基本定义与本质2. 内存管理3. 操作与功能4. 性能特点5. 使用场景6. 相互转换核心区别

python库pydantic数据验证和设置管理库的用途

《python库pydantic数据验证和设置管理库的用途》pydantic是一个用于数据验证和设置管理的Python库,它主要利用Python类型注解来定义数据模型的结构和验证规则,本文给大家介绍p... 目录主要特点和用途:Field数值验证参数总结pydantic 是一个让你能够 confidentl

k8s容器放开锁内存限制问题

《k8s容器放开锁内存限制问题》nccl-test容器运行mpirun时因NCCL_BUFFSIZE过大导致OOM,需通过修改docker服务配置文件,将LimitMEMLOCK设为infinity并... 目录问题问题确认放开容器max locked memory限制总结参考:https://Access

C++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned