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

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

SpringBoot集成XXL-JOB实现任务管理全流程

《SpringBoot集成XXL-JOB实现任务管理全流程》XXL-JOB是一款轻量级分布式任务调度平台,功能丰富、界面简洁、易于扩展,本文介绍如何通过SpringBoot项目,使用RestTempl... 目录一、前言二、项目结构简述三、Maven 依赖四、Controller 代码详解五、Service

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

Python内存优化的实战技巧分享

《Python内存优化的实战技巧分享》Python作为一门解释型语言,虽然在开发效率上有着显著优势,但在执行效率方面往往被诟病,然而,通过合理的内存优化策略,我们可以让Python程序的运行速度提升3... 目录前言python内存管理机制引用计数机制垃圾回收机制内存泄漏的常见原因1. 循环引用2. 全局变

Linux系统管理与进程任务管理方式

《Linux系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优

C++ STL-string类底层实现过程

《C++STL-string类底层实现过程》本文实现了一个简易的string类,涵盖动态数组存储、深拷贝机制、迭代器支持、容量调整、字符串修改、运算符重载等功能,模拟标准string核心特性,重点强... 目录实现框架一、默认成员函数1.默认构造函数2.构造函数3.拷贝构造函数(重点)4.赋值运算符重载函数

Spring Security 前后端分离场景下的会话并发管理

《SpringSecurity前后端分离场景下的会话并发管理》本文介绍了在前后端分离架构下实现SpringSecurity会话并发管理的问题,传统Web开发中只需简单配置sessionManage... 目录背景分析传统 web 开发中的 sessionManagement 入口ConcurrentSess