当前位置: 首页 > news >正文

C++:内存管理 |内存分布|回顾|new/delete底层|实现原理|拓展|定位new|池化技术|总结区别对比

上篇文章:

https://blog.csdn.net/2401_86123468/article/details/153559422?spm=1001.2014.3001.5501

与本文完全相关的C语言:动态内存管理的文章:

https://blog.csdn.net/2401_86123468/article/details/149401001?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522065e0f5ad0bddf75224a00bcc77d1bf3%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=065e0f5ad0bddf75224a00bcc77d1bf3&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-10-149401001-null-null.nonecase&utm_term=malloc&spm=1018.2226.3001.4450https://blog.csdn.net/2401_86123468/article/details/149401001?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522065e0f5ad0bddf75224a00bcc77d1bf3%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=065e0f5ad0bddf75224a00bcc77d1bf3&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-10-149401001-null-null.nonecase&utm_term=malloc&spm=1018.2226.3001.4450

本文阅读可选择略读第二小节内容(更详细的内容见上述链接,C语言主题已详细分析)和池化技术内容(现在接触过早,后续文章会讲)。

前言

程序运行时的内存,处在虚拟进程地址空间中。一段C++程序中,变量,函数,对象均藏在进程的虚拟地址空间里,现代操作系统会为每个进程分配独立的虚拟进程地址空间,在32位系统下,这篇空间最大可达4GB(2^32字节),64位系统则扩展到2^64字节的量级

我们将操作系统类比为“工厂”,那么进程就是“工人”(在分配好的“虚拟地址空间”里执行任务),更关键的是,这片“虚拟地址空间”被精细的划分为多个功能明确的区域。

栈:“编译器自动管控的高效区域”。函数的局部变量、函数调用的上下文(比如返回地址、传递的参数)都存储在这里。栈遵循 “先进后出” 的规则,分配与释放的效率极高,但空间相对有限。

堆:“程序员主导的灵活区域”。需要动态创建的对象(比如用new申请的内存)就存放在堆中。堆的空间更为广阔,但需要程序员手动管理分配与释放—— 若处理不当,“内存泄漏”“重复释放” 等问题会接踵而至。

静态区(数据段):“全局 / 静态变量的‘常驻’区域”。程序启动时,操作系统会为全局变量、静态变量分配内存;直到程序结束,这片内存才会被回收。

常量区(代码段):“存储指令与常量的‘只读’区域”。这里不仅有程序要执行的代码(指令),还包含字符串常量等不可修改的数据。为了保证程序安全,常量区通常是 “只读” 的,防止指令或常量被意外篡改。

1.C/C++内存分布

先阅读下方代码,并给出自己的观点:

回答问题:

详细讲解:

1. globalVar 在哪里?选 C(数据段)

globalVar 是全局变量,全局变量存储在 数据段(静态区)。程序启动时为全局变量分配内存,直到程序结束才会回收这片内存。

2. staticGlobalVar 在哪里?选 C(数据段)

staticGlobalVar 是静态全局变量。“static 修饰全局变量” 时,变量仍存储在数据段(静态区),且作用域限制在当前文件,但生命周期伴随整个程序(从启动到结束)。

3. staticVar 在哪里?选 C(数据段)

staticVar 是函数内的静态局部变量。虽然定义在函数内部,但 “static 修饰局部变量” 会改变其存储位置:从 “栈” 转移到数据段(静态区),且生命周期延长为 “伴随整个程序”(仅初始化一次,函数多次调用时保持值的连续性)。

4. localVar 在哪里?选 A(栈)

localVar 是函数内的普通局部变量(无 static 修饰)。局部变量存储在  中,函数调用时自动为栈上的局部变量分配空间,函数结束时栈空间自动释放,局部变量的生命周期也随之结束。

5. num1 在哪里?选 A(栈)

num1 是函数内的局部数组。数组属于 “复合局部变量”,存储在  中,函数结束时栈上的数组空间会自动释放。

6. char2 在哪里?选 A(栈)

char2 是函数内的局部字符数组char char2[] = "abcd";)。此时是 “用字符串常量初始化字符数组”,会把字符串 "abcd" 的内容拷贝到栈上的数组 char2 中,因此数组 char2 本身存储在栈。

7. *char2 在哪里?选 A(栈)

char2 是栈上的字符数组,*char2 表示数组的首元素char2[0])。首元素属于数组的一部分,因此也存储在中。

8. pChar3 在哪里?选 A(栈)

pChar3 是函数内的局部指针变量。指针变量本身(即存储 “地址” 的变量)存储在  中,函数结束时栈上的指针空间会自动释放(但指针指向的内容是否释放,需看指向的区域)。

9. *pChar3 在哪里?选 D(代码段 / 常量区)

pChar3 是 const char* 类型,指向字符串常量 "abcd"字符串常量(如 "abcd")存储在 代码段(常量区),该区域是 “只读” 的,程序运行时不会被修改。

10. ptr1 在哪里?选 A(栈)

ptr1 是函数内的局部指针变量。和 pChar3 类似,指针变量本身(存储 malloc 分配的堆内存地址)存储在  中,函数结束时栈上的指针空间自动释放。

11. *ptr1 在哪里?选 B(堆)

ptr1 通过 malloc 函数动态分配内存malloc/calloc/realloc 或 C++ 的 new 都用于堆内存分配)。动态分配的内存存储在  中,需要手动通过 free(或 C++ 的 delete)释放,否则会造成 “内存泄漏”。


2.C语言中动态内存管理方式:malloc / calloc / realloc / free

2.1malloc/calloc/realloc的区别

1. malloc 函数

  • 功能:从堆中分配一块指定字节数的内存。
  • 初始化:分配的内存不初始化,内容是随机的 “垃圾值”(不确定)。
  • 语法void* malloc(size_t size);size 为需要分配的总字节数)。
  • 返回值:成功时返回分配内存的首地址(需强制转换为目标类型指针);失败时返回 NULL
  • 适用场景:仅需分配内存、不关心初始内容的场景(比如后续会立即给内存赋值)。

2. calloc 函数

  • 功能:从堆中分配连续的、指定数量 × 单个大小的内存(总大小为 num * size)。
  • 初始化:会将分配的内存全部初始化为 0,无 “垃圾值”。
  • 语法void* calloc(size_t num, size_t size);num 是元素数量,size 是单个元素的字节大小)。
  • 返回值:成功时返回分配内存的首地址(需强制转换);失败时返回 NULL
  • 适用场景:需要内存初始为 0 的场景(比如数组、结构体的初始化)。

3. realloc 函数

  • 功能调整已分配内存块的大小(可扩大或缩小)。
  • 初始化
    • 扩大内存且需要 “新分配” 部分内存(如原地址后空间不足,需换地址),新分配的内存会初始化为 0
    • 缩小内存,原有数据会保留,被 “裁掉” 的部分数据丢失。
  • 语法void* realloc(void* ptr, size_t new_size);ptr 是原内存指针,new_size 是新的总字节数)。
  • 返回值:成功时返回调整后内存的指针(可能是原地址,也可能是新地址);失败时返回 NULL,且原内存块不会被释放(仍保留原数据)。
  • 特殊情况
    • 若 ptr 为 NULLrealloc 等价于 malloc(new_size)(全新分配内存);
    • 若 new_size 为 0realloc 等价于 free(ptr)(释放原内存,返回 NULL)。
  • 适用场景:已分配内存后,需要扩大 / 缩小内存的场景(比如动态数组的扩容)。

总结对比

特性malloccallocrealloc
功能分配指定字节的内存分配并初始化 0 的内存(num×size调整已分配内存的大小
初始化不初始化(内容为垃圾值)初始化为 0扩展时新内存初始为 0;缩小时原数据保留
参数1 个(总字节数 size2 个(元素数量 num、单个大小 size2 个(原指针 ptr、新总字节数 new_size
返回值分配内存的指针(失败返回 NULL分配内存的指针(失败返回 NULL调整后内存的指针(失败返回 NULL,原内存保留)
适用场景只分配内存,不关心初始值需要初始为 0 的内存(如数组)调整已有内存的大小(如动态扩容)

2.2malloc的实现原理

https://www.bilibili.com/video/BV117411w7o2/?spm_id_from=333.788.videocard.0


 

3.C++内存管理方式

C++拥有自己的内存管理方式:通过new和delete操作符进行动态内存管理

3.1new/delete操作内置类型

C++中并没有提供类似realloc扩容的方式,需要自己手工扩容。

3.2new和delete操作自定义类型

既然c++中也可以使用c语言中的malloc等,那为什么还要创建new这类方式呢?

原因是:C语言中的方式只开空间,不调用构造初始化

使用C++构建链表更方便:

struct ListNode
{ListNode* _next;int _val;ListNode(int val):_next(nullptr), _val(val){ }
};
int main()
{ListNode* n1 = new ListNode(1);ListNode* n2 = new ListNode(2);ListNode* n3 = new ListNode(3);return 0;
}

3.3new与malloc的返回区别

malloc失败会返回空指针,new失败会抛异常,而对于异常,我们需要捕获,见代码:

void func()
{int i = 1;int* ptr = nullptr;do {if (i == 11105){int x = 0;}ptr = new int[1024 * 1024 ];cout << i++ << ":" << ptr << endl;} while (ptr);cout << i++ << ":" << ptr << endl;
}
int main()
{try{func();}catch (const exception& e){cout << e.what() << endl;}return 0;
}

注意:此时我知道会在11105处抛异常,可以写代码中被框起来的if语句,此作用类似于打断点。

4.new/delete底层


new在面对自定义的类类型时,需要两步:

1.在堆上开空间(从底层上讲,new使用的依旧是malloc,可以认为new是malloc的再包装)

2.调用构造函数


delete是free的封装


4.1operator new 与 operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operatornew和operatordelete是系统提供的全局函数,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来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete最终是通过free来释放空间的。

5.new和delete的实现原理

5.1内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new和delete申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

5.2自定义类型

转到反汇编:

new的原理

1.调用operatornew函数申请空间
2.在申请的空间上执行构造函数,完成对象的构造

delete的原理

1.在空间上执行析构函数,完成对象中资源的清理工作
2.调用operatordelete函数释放对象的空间

newT[N]的原理

1.调用operator new[函数,在operator new』中实际调用operator new函数完成N个对象空间的申请
2.在申请的空间上执行N次构造函数

delete[]的原理

1.在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2.调用operator delete释放空间,实际在operator delete[中调用operator delete来释放空间

6.拓展

6.1为什么多出4个字节

根据代码,此处应该有40个字节,但转到反汇编后我们发现,居然有44个字节,这是为什么呢?

实际上,这多出的四个字节,是需要存数组中对应对象的个数:

6.2为什么又“正常”了

此时又是40个字节

看代码,此时没有析构,只需要调用默认析构,且代码中没有什么资源需要释放,因此聪明的编译器不会多开字节去存个数。

7.定位new表达式(placement-new)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

使用格式:

new (place_address) type或者new (place_address) type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表

使用场景:

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。


7.1池化技术

池化技术(Pooling) 是一种预分配资源、重复利用的技术思想。

核心目标是:

避免频繁的创建与销毁(尤其是代价昂贵的资源,如内存块、数据库连接、线程、文件句柄等)。

举几个常见的“池”的类型:

类型用途示例
连接池(Connection Pool)复用数据库连接Java JDBC连接池、Redis连接池
线程池(Thread Pool)重用线程执行任务std::thread + 任务队列
内存池(Memory Pool)复用内存块Boost Pool、tcmalloc、jemalloc、Google 自研 Arena

7.2内存池

C++ 默认的 new / deletemalloc / free 都要经过:

  • 内核空间与用户空间切换;

  • 内存分配器(glibc malloc)的锁;

  • 较高的碎片化风险。

内存池 则是:

预先申请一大块内存,然后自己在这块区域内进行小块分配和回收。

内存池的基本结构

  1. Pool(池体):管理大块连续内存。

  2. Block(块):将池体划分为多个固定大小的块。

  3. Free List(空闲链表):记录哪些块当前可用。

7.3Google 的内存池技术(以 tcmalloc为例)

tcmalloc:高性能多线程内存分配器

  • TCMalloc(Thread-Caching Malloc) 是 Google 为多线程环境优化的内存分配器。

  • 它解决了 glibc malloc 的全局锁问题。

原理简述:

  • 每个线程维护一个本地缓存(Thread Cache),避免锁竞争;

  • 按对象大小分级(size class);

  • 小对象使用内存池分配,大对象直接系统分配;

  • 回收时放回对应的缓存。

性能对比:

分配器多线程性能小块分配速度
glibc malloc慢(锁争用)一般
jemalloc稳定
tcmalloc极快极快

在 Google 的服务器程序、Chrome 浏览器中广泛使用。

7.4总结对比

方案优点缺点适用场景
普通 new/delete简单,自动管理慢,碎片多小规模
自建内存池快速复用管理复杂固定大小对象
TCMalloc / Jemalloc通用高性能较大依赖服务端并发系统
Google Arena超快,批量释放不支持单独回收短生命周期对象

8.malloc/free和new/delete的区别

可从用法,核心特性,原理去理解

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在释放空间前会调用析构函数完成空间中资源的清理释放

本章完。

http://www.dtcms.com/a/503372.html

相关文章:

  • 上街网站建设做语文高考题网站
  • 前端基础二、CSS(一)、CSS基础知识
  • 【MySQL】第三章 运算符
  • 智能机器人梯控系统(含二维码/刷卡/人脸识别)安装布线指南,结合工程规范与安全要点进行结构化优化,内容清晰、可操作性强
  • 突破 @Valid 局限!Spring Boot 编程式验证深度解析与复杂场景实战
  • 盐城市规划建设局网站做北美市场用哪个网站
  • 【攻防实战】系列二-使用metasploit打穿某集团(下)
  • 单词搜索 II · Word Search II
  • css `dorp-shadow`
  • 做网站内容管理器要吗免费的网站制作
  • 有限理性的边界与超越:人类如何在认知局限中走向更明智的决策
  • 【参赛心得】从“碰一碰”到“服务流转”:HarmonyOS创新赛金奖作品“智游文博”全流程复盘!
  • 网站推广服务报价表动态门户网站建设价格
  • 网站建设公司的排名濮阳市做网站
  • 【金仓数据库产品体验官】实战测评:电科金仓数据库接口兼容性深度体验
  • RabbitMQ 入门:基于 AMQP-CPP 的 C++ 实践指南与二次封装
  • google外贸网站推广企业的网站公告怎么制作
  • Spring 源码学习(十四)—— HandlerMethodArgumentResolver
  • Git拉取代码报无权限的错误处理方案
  • 棋牌网站管理后台嗅探查找方法(2025最新)
  • 沈阳微信网站建设大连网站建设开发
  • 中英文企业网站模板wordpress插件 标签
  • 生成式引擎优化(GEO):五大 AI 引擎赋能多场景的技术与实践指南
  • 从邮票到Labubu:四十年中国收藏与潮流风潮的演变逻辑
  • 天猫网站左侧导航是怎么做的青岛建站推广
  • Linux中I2C常见问题三
  • C++初阶(14)list
  • python进阶刷题8
  • 完成职教集团网站建设唐山市做网站
  • 19.7 ChatPPT v2.0语音识别实战:3秒极速响应+88.7%准确率的Whisper模型黑科技