深入解析:C++基础(21)——内存管理

news/2025/10/9 18:57:41/文章来源:https://www.cnblogs.com/yxysuanfa/p/19131763

目录

前言

C/C++的内存分布

C语言里的动态内存管理方式:malloc、calloc、realloc和free

C++的动态内存管理的方式

new和delete操作内置类型

new和delete操作自定义类型

operator new和operator delete函数

new和delete的实现原理

对于内置类型

对于我们的自定义类型

定位new表达式(placement-new)

常见的面试题

第一题

第二题

第三题


前言

我们在学习C++的时候,往往需要对于管理内存有一定的理解,这也是我们开发高性能应用程序至关重要的一点,同时这也是我们在笔试面试题中比较常考的点。这一节我们将介绍内存分布,内存管理和一些常见的面试题。

C/C++的内存分布

#include
using namespace std;
int globalVar = 1;
static int staticGlobalVar = 1;
int main() {
static int staticVar = 1;
int localVar = 1;
int nums[10] = {1, 2, 3, 4, 5};
char str1[] = "hello";
char str2[] = "hello";
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(ptr2);
return 0;
}

其实这里的内容我在C语言的时候也写过一样的,大家可以来分析一下这上面的代码中的各个变量分别在那个区域中,下面就是我们的答案了。

这里说明一下:

1、栈也叫堆栈,用来存储非静态局部变量/函数参数/返回值等,栈是向下增长的。

2、内存映射段是高效I/O的映射方式,用来装载一个共享的动态内存库。用户可以使用系统的接口来创建共享内存,作为我们的进程间通行的管道。(在Linux中)

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

4、数据段是用来存储全局数据和静态数据的,因此也叫静态区。

5、代码段是用来存储可执行的代码和只读的常量的,因此也叫常量区。

也许有的同学对于我们的栈是从上面的高地址往下面的低地址增长和从下面的低地址向上面的高地址增长这句话不太有什么体会,我们这里还写一个代码来演示一下:

代码如下:

#include
using namespace std;
int main() {
int a = 1;
int b = 2;
int* c = (int*)malloc(sizeof(int)*5);
int* d = (int*)malloc(sizeof(int)*5);
cout << "stack:" << endl;
cout << &a << endl;
cout << &b << endl;
cout << "heap:" << endl;
cout << c << endl;
cout << d << endl;
return 0;
}

测试效果如下:

通过这里的代码展示我们可以看到我们的栈区先开辟的a的空间的地址要比后开辟的b的空间的地址要大,在我们的堆区上,我们先开辟的c的空间的地址要比后开辟的d空间的地址要小。

C语言里的动态内存管理方式:malloc、calloc、realloc和free

1、malloc

我们使用的malloc函数就是用开开辟指定字节数的内存空间,开辟成功就返回我们开辟空间的首地址,开辟失败就返回我们的NULL。

函数原型如下:

void* malloc(size_t size);

2、calloc

我们的calloc函数和我们的malloc函数类似,也是传入我们要开辟的空间的大小,但是我们的calloc函数可以将开辟好的空间里面的每一个字节都初始化成0。

函数原型如下:

void* calloc(size_t num, size_t size);

3、realloc

这里的realloc就和上面两个不一样了,我们的realloc函数重新调整我们已经分配好的内存块的大小的,如果增加大小就会将原来内存块的数据复制到我们的新的位置,如果减小大小就会把我们的数据进行截断处理。

函数原型如下:

void* realloc(void* ptr, size_t size);

我们这里有一常考的知识点,就是扩充方式的三种情况了:

第一种情况:原地扩容,如果我们需要扩展的空间后面有足够的空间可以扩展,我们这个时候就可以直接在后面扩展,然后返回原来的空间的首地址即可。

第二种情况:异地扩容,如果我们需要扩展的空间的后面没有足够的空间可以用来扩展,那么这个时候我们就要重新找到一个满足要求的空间,将原来空间里面的数据拷贝到我们找到的空间里面,并且这个时候我们还要将原来的空间释放掉,同时返回我们新找到空间的首地址。

第三种情况:扩充失败,我们需要扩展的空间后面的空间不足,并且我们的堆区里面也没有足够的空间重新开辟,这个时候我们就要返回NULL了。

4、free

我们这里的free函数的作用就是将我们的上面三个函数申请的空间给释放掉。

这里如果你还想了解更多,可以直达这里动态内存管理

C++的动态内存管理的方式

我们C++兼容了C语言,所以上面的这些函数在C++文件里面也是可以用的,但是我们的C++有自己更好的兼容C++的管理方式。首先我们要介绍的就是我们C++内存管理的两个重要的操作符:new和delete。

new和delete操作内置类型

一、单个类型的申请

示例:

// 单个类型的申请
int* p = new int;
delete p;

等价写法(C语言):

// C语言写法
int* p_c = (int*)malloc(sizeof(int));
free(p_c);

二、多个类型的申请

示例:

// 多个类型的申请
int* p = new int[5];
delete[] p;

等价写法(C语言):

// C语言的写法
int* p_c = (int*)malloc(sizeof(int) * 5);
free(p_c);

三、单个类型的申请并初始化

示例:

// 申请单个类型并初始化位5
int* p = new int(5);
delete p;

等价写法(C语言):

// C语言写法
int* p_c = (int*)malloc(sizeof(int));
*p_c = 5;
free(p_c);

四、多个类型的申请并初始化

示例:

// 申请多个类型并初始化
int* p = new int[5]{0, 1, 2, 3, 4};
delete[] p;

等价写法(C语言):

// C语言写法
int* p_c = (int*)malloc(sizeof(int) * 5);
for(int i = 0; i < 5; i++) {
p_c[i] = i;
}
free(p_c);

new和delete操作自定义类型

首先我们要有一个自定义的类型:

class Person {
public:
Person() : age(0) {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person" << endl;
}
private:
int age;
};

一、申请单个自定义类型

示例:

// 申请单个自定义类型
Person* p = new Person;
delete p;

测试效果:

C语言实现:

// C语言写法
Person* p_c = (Person*)malloc(sizeof(Person));
free(p_c);

测试效果:

二、申请多个自定义类型

示例:

// 申请多个自定义类型
Person* p = new Person[5];
delete[] p;

测试效果:

C语言实现:

// C语言的实现
Person* p_c = (Person*)malloc(sizeof(Person) * 5);
free(p_c);

测试效果:

敲黑板:

我们这里的new会调用我们的构造函数,delete会调用我们的析构函数,但是我们的malloc和free是不会的。

这里我们总结一下:

1、我们的C++和C语言在针对内置类型的时候使用的操作符没区别。

2、对于我们的自定义类型,我们的new会调用构造函数并开空间,delete会调用析构函数并释放空间,我们的malloc和free只会申请和释放空间。

operator new和operator delete函数

我们这里要清楚,我们的new和delete在底层实际上就是调用了operator new和operator delete来申请和释放空间的,而我们的operator new和operator delete的用法也是和我们的malloc和free一样的。

示例:

int* p = (int*)operator new(sizeof(int) * 5);
operator delete(p);
// 等价于
int* p_c = (int*)malloc(sizeof(int) * 5);
free(p_c);

我们的operator new和我们的operator delete实际上也不是我们专门创造出来的,他们的底层实际上是封装了malloc函数和我们的free函数,不过我们在原来的基础上面加入了抛异常机制。

图示:

相关的源码:

operator new:

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
void* p;
// 通过 malloc 申请内存空间
while ((p = malloc(size)) == nullptr)
{
// 如果 malloc 返回 nullptr,表示内存申请失败
// 调用用户自定义的空间不足应对措施
if (_callnewh(size) == nullptr)
{
// 如果没有设置空间不足的应对措施,就抛出 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));
// 如果指针为 null,不做任何操作
if (pUserData == nullptr)
return;
// 获取内存块的头部信息,锁住内存块以防其他线程操作
_mlock(_HEAP_LOCK);
__TRY
{
// 获取内存块的头部指针
pHead = pHdr(pUserData);
// 验证内存块的类型
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
// 调用 _free_dbg 释放内存
_free_dbg(pUserData, pHead->nBlockUse);
}
__FINALLY
{
// 解锁内存块
_munlock(_HEAP_LOCK);
}
__END_TRY_FINALLY
}

new和delete的实现原理

对于内置类型

对于内置类型来说,其实我们的new/delete和malloc/free是基本上类似的,但是不一样的是,我们的new/delete申请的是单个元素,加上了[ ]申请的就是多个类型的连续的空间了,此外我们的malloc申请失败了会返回NULL,但是我们的new申请失败了会执行我们的抛异常机制。

对于我们的自定义类型

new的原理

1、调用我们的operator new函数申请空间。

2、在申请好了的空间上面执行我们的构造函数,完成对于对象的构造。

delete的原理

1、在开辟的空间上面使用析构函数,对于资源进行清理。

2、调用我们的operator delete函数释放我们的对象的空间。

new T[N]的原理

1、调用了我们的operator new[ ]函数,在我们的operator new[ ]函数中实际使用的还是我们的operator new函数,只不过是完成N个对象的空间的申请。

2、在申请好的空间上面调用我们的析构函数N次。

delete[ ] 的原理

1、在我们开辟好了的空间上面调用执行N次我们的析构函数,完成对于N个对象的资源的管理操作。

2、调用我们的operator delete[ ]函数,实际上就是调用多次opreator delete函数完成对于N个对象空间的释放。

定位new表达式(placement-new)

我们这里的定位new表达式主要在已经分配的内存空间中调用构造函数来初始化对象的。

使用的格式有两种:

第一种:

new(place_address)type

这里的place_addree是一个指针。

第二种:

new(place_address)type(initializer-list)

这里的initializer-list是类型的初始化列表。

使用的场景:

我们的定位new表达式实际上一般都是结合我们的内存池来使用的,我们的内存池分配的内存没有初始化,所以就要我们来自定义类型的对象,需要使用定位new表达式来显示的调用构造函数来初始化了。

示例代码:

#include
using namespace std;
class test {
public:
test(int a = 0) : x(a) {
cout ~test();
p2->~test();
return 0;
}

测试效果:

敲黑板:

我们这里在没有使用定位new表达式之前malloc申请的空间还不是一个对象,只不过是和test大小一样大的空间而已。

常见的面试题

第一题

malloc/free和new/delete的区别是什么?

共同点:都是从堆上申请的空间,并且都是需要用户手动释放的。

不同点:

1、malloc和free是函数,new和delete是操作符。

2、malloc申请的空间并不会初始化,但是我们的new的空间可以初始化。

3、malloc申请空间的时候,需要手动计算空间的大小并传递,new只需要在其后面跟上类型即可,多个对象就是用[N](N是对象的个数)。

4、malloc的返回值位void*类型,在使用的时候需要进行强制类型的转换,但是我们的new不需要,因为new后面有类型。

5、malloc申请空间失败就返回NULL,所以我们需要进行空指针判断,但是我们的new使用的时候有我们的异常捕捉机制在。

6、申请自定义类型的对象的时候,我们的malloc/free只会开辟空间,不会调用我们的构造函数和析构函数,但是我们的new在申请空间之后会调用构造函数用来初始化,delete会在释放空间之前调用我们的析构函数完成空间中的资源的释放。

第二题

什么是内存泄露,内存泄露的危害是什么,内存泄露怎么解决?

内存泄露:

简单的说就是我们申请了一块空间,但是使用完了之后,我们并没有按要求释放掉。有下面几种典型情况:

1、new和malloc申请资源并使用后,没用调用delete和free释放。

2、子类继承了父类,但是父类的析构函数不是虚函数。

3、Windows句柄资源使用后没有释放。

内存泄露的危害:
如果我们长期的出现程序的内存泄露的问题,影响会非常的大,比如,我们的操作系统,后台的服务出现内存的泄露会导致响应慢,最终导致死机。

处理方案

1、我们要有良好的编码习惯,使用内存分配的函数,使用完了就要记得使用对应的函数释放。

2、将分配的内存的指针以链表的形式进行管理,使用完毕后从链表中删除,程序结束可以检查链表。

3、使用智能指针(RAII)。

4、使用一些常见的工具插件,比如ccmalloc、Dmalloc、Leaky、Valgrind等等。

第三题

内存泄露的分类?

我们的C/C++里面主要是关心两种方面的内存泄露:

第一个:堆内存的泄露

堆内存指的是程序执行中使用了malloc、calloc、realloc、new等从堆里面分配出来的一块内存,用了之后就要使用对应的free或是delete释放掉,如果这部分空间没有被释放就会产生Heap Leak。

第二个:系统资源的泄露

我们的程序使用系统分配的资源(比如套接字、文件描述符和管道等)没有使用对应的函数释放掉,导致了系统资源的浪费,最终导致系统不稳定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/933783.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#性能优化基础:内存诊断(dump)

接上一篇:C#性能优化基础:垃圾回收机制本文说下怎么去查找内存问题,举个例子,我们有这样的一段程序:namespace ConsoleApp1{internal class Program{static List<Demo> Demos { get; } = new List<Demo&…

2025年企业级LLM内容安全防护指南:鉴冰AI FENCE流式网关技术深度解析

2025年企业级LLM内容安全防护指南:鉴冰AI FENCE流式网关技术深度解析随着生成式AI在企业关键业务中的深度应用,LLM输出违规内容防护已从技术选项升级为合规刚需。AI-FOCUS团队推出的鉴冰AI FENCE(AI安全围栏)采用流…

完整教程:FPGA学习笔记——图像处理之亮度调节(Gamma)

完整教程:FPGA学习笔记——图像处理之亮度调节(Gamma)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas…

Kubernetes Ingress:管理集群外部访问的入口网关

在k8s之服务Service章节,我们详细的介绍了Service的组成以及相关的原理。Service可以将自身的服务暴露出去,给集群内部服务或者给外部服务去使用,或者将外部服务分装为一个service,供给集群内部服务使用。而今天介…

搜索选讲

前言 我太菜了,如有没写清楚的地方大家轻喷. 爆搜 P4467 k短路 Hint:沿用次短路的思路(P1491). 不能经过重复的点是一个很强的限制,直接搜无论怎么剪枝都会被卡爆. 由于没有负权边,最短路必然不会经过重复的点,…

深入解析:在Linux中部署tomcat

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

vue打包的项目,从根目录进去路由可访问,浏览器直接打开这个路由不可访问

Vue 项目路由访问问题分析 您描述的问题是 Vue 打包后的项目在直接访问子路由时出现 404 错误,而从首页导航可以正常访问。这是一个常见的 Vue 路由配置问题。 问题原因 这是因为您使用的是 Vue Router 的 history 模…

深入解析:Docker容器化部署简要指南

深入解析:Docker容器化部署简要指南2025-10-09 18:29 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !im…

IObit Uninstaller一款强大的卸载工具!IObit Uninstaller卸载工具,IObit Uninstaller下载安装教程

软件介绍 IObit Uninstaller 是一款强大且实用的卸载工具。它能有效替代 Windows 自带卸载功能,,不残留相关文件与注册表信息,防止因残留导致系统变慢或出错。软件支持多种卸载方式,常规卸载适用于大多数程序;批量…

网络配置不再难:4G/Wi-Fi/以太网/虚拟网卡全指南

多种网络接口并存的时代,掌握4G、Wi-Fi、以太网和虚拟网卡的配置是必备技能。本文系统讲解各类连接方式的设置方法,助你轻松应对复杂网络场景。 网络适配器,它的一个更广为人知的名字是——网卡。 在应用开发中我们…

2025开关按钮厂家最新推荐榜:开关按钮,带灯开关按钮,防水开关按钮,防爆开关按钮,防腐开关按钮等全种类覆盖,高品质设计与卓越性能口碑之选

在当今科技飞速发展的时代,开关按钮作为各种电子设备、电气系统中不可或缺的基础元件,其重要性不言而喻。无论是工业自动化生产线上的精密控制设备,还是日常生活中的家用电器,开关按钮都在默默地发挥着关键作用。随…

一种排查java.lang.OutOfMemoryError: Metaspace的方法

有现场反应k8s中的java应用出现java.lang.OutOfMemoryError: Metaspace,但是没有提供hprof转储文件,所以本地排查该问题。 Metaspace区域的内存溢出错误,通常意味着 JVM 中加载的类元数据超出了限制。 jstat -class…

MDX Blog Post

Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).:::tipUse the power of React to create interactive blog posts....Blog …

Long Blog Post

This is the summary of a very long blog post,Use a `` comment to limit blog post size in the list view.This is the summary of a very long blog post,Use a `` comment to limit blog post size in the list …

First Blog Post

Lorem ipsum dolor sit amet...Lorem ipsum dolor sit amet......consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolo…

本站点即将在2025年改变研究方向和目标

由于当前部分社区发展已经朝向不可控领域,以及流浪的猎人计划做一些其他的东西,预计会在今年内开始对该站点的研究方向进行一些改变。 我们会在未来降低一些特定技术研究的公开,如果一些东西最后公开出来变成了“外…

详细介绍:内置高压MOS的智能调光方案:AP5126 LED降压恒流驱动芯片

详细介绍:内置高压MOS的智能调光方案:AP5126 LED降压恒流驱动芯片pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: &quo…

实用指南:12_OkHttp初体验

实用指南:12_OkHttp初体验pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", …

深入解析:llm的ReAct

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

(AE)Adobe After Effects 2025 视频后期制作软件!安装包永久免费免激H解锁版下载与图文详细安装教程!!

软件介绍 Adobe After Effects 2025 作为一款专业的动态图形与视觉特效软件,在影视后期制作领域地位显著。它提供了丰富且强大的功能,能助力创作者将创意变为现实。它支持2D及3D动画,透过图层控制音频与视频合成效果…