【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

相关文章

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

安全管理体系化的智慧油站开源了。

AI视频监控平台简介 AI视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。用户只需在界面上进行简单的操作,就可以实现全视频的接入及布控。摄像头管理模块用于多种终端设备、智能设备的接入及管理。平台支持包括摄像头等终端感知设备接入,为整个平台提

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)