C/C++ 内存管理

1.C/C++内存分布

 

sizeof和strlen有什么区别: 

本质区别

特性sizeofstrlen
类型运算符(编译时计算)库函数(运行时计算)
作用对象变量、数据类型、表达式仅限以 \0 结尾的字符串(char* 或字符数组)
功能计算对象/类型占用的内存字节数计算字符串的字符长度(不含 \0
空字符处理包含字符串末尾的 \0不包含 \0
性能编译时确定,无运行时开销需遍历字符串直到 \0,有线性时间复杂度
  • sizeof 用于内存分配:计算变量、类型或表达式占用的内存字节数。

  • strlen 用于字符串长度:统计 \0 前的字符数量。

  • 永远不要对非字符串使用 strlen:否则可能引发内存越界或未定义行为。

  • 动态内存分配时:若需存储字符串,内存大小应为 strlen(str) + 1(为 \0 预留空间)。

说明:

  1. 又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。 用户可使用系统接口创建共享内存,做进程间通信。(linux)
  3. 用于程序运行时的动态内存分配,堆是可以向上增长的。
  4. 数据段--存储全局数据和静态数据
  5. 代码段--可执行的代码/只读常量

2.C语言中动态内存管理方式

void Test()
{int* p1 = (int*)malloc(sizeof(int));free(p1);int* p2 = (int*)calloc(4, sizeof(int));int* p3 = (int*)realloc(p2, sizeof(int) * 10);free(p3);
不需要释放p2;
若原内存块可以原地扩展:直接扩大内存,p3 的地址与 p2 相同。
若需要重新分配新内存块:将原数据复制到新内存块,并自动释放原内存块(p2 的内存),p3 指向新地址。
}

2.1 malloc

  • 功能:分配指定字节数的未初始化内存。

  • 参数void* malloc(size_t size);

    • size:需要分配的字节数。

  • 特点

    • 分配的内存内容是未初始化的(可能包含垃圾值)。

    • 效率较高,因为它不进行内存初始化。

2.2 calloc

  • 功能:分配指定数量和大小的内存块,并初始化为零。

  • 参数void* calloc(size_t num, size_t size);

    • num:元素数量。

    • size:每个元素的字节大小。

  • 特点

    • 分配的内存会被自动初始化为零(所有位为 0)。

    • 适合需要初始化的场景(如数组、结构体)。

    • 相比 malloc 稍慢,因为需要额外初始化步骤。

2.3 realloc

  • 功能:调整已分配内存的大小(扩大或缩小)。

  • 参数void* realloc(void* ptr, size_t new_size);

    • ptr:指向之前分配的内存块的指针(若为 NULL,则行为等同于 malloc)。

    • new_size:新的总字节数。

  • 特点

    • 如果新大小大于原内存块,新增部分内容未初始化。

    • 可能会在内存中移动原有数据到新位置(需用返回值更新指针)。

    • 若 new_size 为 0,则等效于 free(ptr)(但行为依赖实现)。

2.4 对比总结

特性malloccallocrealloc
初始化未初始化初始化为零新增区域未初始化
参数总字节数 (size)元素数量和大小 (num, size)原指针和新字节数 (ptr, new_size)
用途分配未初始化内存分配初始化内存(如数组)调整已分配内存的大小
性能较快(不初始化)稍慢(需初始化)取决于是否需要数据迁移
内存不足返回值NULLNULLNULL(原内存仍存在)

2.5 malloc的实现原理

malloc 是 C 语言中动态内存分配的核心函数,其实现原理涉及操作系统内存管理、堆内存分配策略和数据结构设计。以下是其核心实现原理的逐步解析:


1. 内存管理的基本框架

程序运行时,操作系统会为其分配虚拟内存空间,其中堆(Heap)是动态内存分配的区域。malloc 负责从堆中分配内存块,free 负责释放内存块。堆的管理依赖以下关键机制:

  • 堆的扩展与收缩:通过系统调用(如 sbrk 或 mmap)调整堆的大小。

  • 内存块管理:将堆划分为多个内存块,每个块包含元数据(如块大小、状态)和用户数据区。

  • 空闲块组织:通过链表、树或其他数据结构管理空闲块,提高分配效率。


2. 内存块的结构

每个内存块(包括已分配和空闲块)通常包含两部分:

  1. 元数据(Header)

    • 记录块的大小(size)。

    • 标记块是否空闲(is_free)。

    • 其他管理信息(如前驱/后继指针,用于空闲链表)。

  2. 用户数据区:返回给用户的实际可用内存地址。


3. 空闲块管理策略

为了高效管理空闲内存块,malloc 实现通常采用以下数据结构:

(1) 隐式空闲链表(Implicit Free List)
  • 原理:通过块头部的 size 字段遍历所有内存块(包括已分配和空闲块)。

  • 优点:实现简单。

  • 缺点:分配时需要遍历整个链表,时间复杂度为 O(n)。

  • 示例

    struct block_header {size_t size;int is_free;
    };
(2) 显式空闲链表(Explicit Free List)
  • 原理:仅维护空闲块的链表,通过指针(prev/next)连接。

  • 优点:减少遍历时间,分配时间复杂度接近 O(1)。

  • 缺点:需额外空间存储指针。

(3) 分离空闲链表(Segregated Free List)
  • 原理:将空闲块按大小分类到多个链表中(如小块、中块、大块)。

  • 优点:针对不同大小请求快速匹配,减少搜索时间。

  • 现代实现glibc 的 ptmalloc2 和 jemalloc 均采用此策略。


4. 内存分配策略

当用户请求分配内存时,malloc 会从空闲链表中选择一个合适的块:

(1) 首次适应(First Fit)
  • 遍历链表,选择第一个足够大的空闲块。

  • 优点:简单快速。

  • 缺点:可能产生外部碎片。

(2) 最佳适应(Best Fit)
  • 遍历链表,选择最小的足够大的空闲块。

  • 优点:减少内存浪费。

  • 缺点:遍历时间长,可能产生微小碎片。

(3) 最坏适应(Worst Fit)
  • 选择最大的空闲块进行分割。

  • 优点:减少外部碎片。

  • 缺点:不利于后续的大块分配。


5. 内存分配流程

  1. 对齐要求:根据系统要求(如 8 字节对齐)调整请求大小。

  2. 搜索空闲块

    • 在空闲链表中查找足够大的块。

    • 若找到,分割块(剩余部分作为新空闲块)或直接分配整个块。

  3. 扩展堆:若没有足够空闲块,通过 sbrk 或 mmap 扩展堆。

  4. 返回地址:返回用户数据区的起始地址。


6. 内存释放与合并

free 释放内存块时:

  1. 标记为空闲:将块头部的 is_free 标记为 1。

  2. 合并相邻空闲块:检查前驱和后继块是否空闲,合并以减少碎片。


7. 实际实现细节(以 glibc 的 ptmalloc2 为例)

  • 多线程支持:使用 arenas 隔离不同线程的内存池,避免锁竞争。

  • 大块内存分配:超过阈值(如 128KB)时,直接通过 mmap 分配,绕过堆管理。

  • Fast Bins:维护小内存块(如 16-80 字节)的快速链表,避免频繁合并。


8. 性能与碎片问题

  • 内部碎片:分配块的实际大小大于请求大小(因对齐或元数据占用)。

  • 外部碎片:空闲内存分散,无法满足连续大块请求。

  • 优化策略

    • 合并空闲块。

    • 使用分离空闲链表减少搜索时间。

    • 延迟合并(如 Fast Bins 不立即合并小内存块)。

总结:
malloc
 通过 链表管理空闲内存块,按策略分配内存,释放时合并相邻块,依赖系统调用扩展堆,并优化多线程与碎片问题。

 3.C++内存管理方式

C语言内存管理方式C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

3.1 new/delete操作内置类型

void Test()
{//动态申请一个int类型的空间int* ptr1 = new int;//动态申请一个int类型的空间并初始化为10 int* ptr2 = new int(10);//动态申请10个int类型的空间int* ptr3 = new int[10];delete ptr1;delete ptr2;delete[] ptr3;
}

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],匹配起来用。

3.2new和delete操作自定义类型

class A
{
public:A(int a=0):_a(a){cout << "A(): " << this << endl;}~A(){cout << "~A(): " << this << endl;}
private:int _a;
};
int main()
{A* p1 = (A*)malloc(sizeof(A));cout << "new:" << endl;A* p2 = new A(1);free(p1);delete p2;A* p3 = (A*)malloc(sizeof(A) * 3);cout << "new:" << endl;A* p4 = new A[3];free(p3);delete[] p4;return 0;
}

注意:在申请自定义类型时,new会自动调用构造函数,delete会自动调用析构函数,而malloc于free不会。new/delete出来用法上,其他没什么区别。

 4.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来申请空间,如果
malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施
就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

5.new和delete的实现原理

5.1内置类型

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

5.2 自定义类型
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[]释放空间,实际在operator delete[]中调用operator delete来释放空间

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

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如
果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

7. 常见面试题

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

7.2 内存泄漏

7.2.1 什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内
存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对
该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死。

void MemoryLeaks()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}

7.2.2 内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak)
    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
  • 系统资源泄漏
    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

7.2.3 如何检测内存泄漏

在vs下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks() 函数进行简单检测,该
函数只报出了大概泄漏了多少个字节,没有其他更准确的位置信息。 

7.2.4如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
  2.  采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结一下:
内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄
漏检测工具

 

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

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

相关文章

【C语言】:学生管理系统(多文件版)

一、文件框架 二、Data data.txt 三、Inc 1. list.h 学生结构体 #ifndef __LIST_H__ #define __LIST_H__#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <time.h>#define MAX_LEN 20// 学生信息…

【Spring】第三弹:基于 XML 获取 Bean 对象

一、获取 Bean 对象 1.1 根据名称获取 Bean 对象 由于 id 属性指定了 bean 的唯一标识&#xff0c;所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。 1.确保存在一个测试类&#xff1a; public class HelloWorld {public void sayHello(){System.out.println(&quo…

Easysearch 索引生命周期管理实战

如果你的使用场景是对时序型数据进行分析&#xff0c;可能你会更重视最新的数据&#xff0c;并且可能会定期对老旧的数据进行一些处理&#xff0c;比如减少副本数、forcemerge、 删除等。Easysearch 的索引生命周期管理功能&#xff0c;可以自动完成此类索引的管理任务。 创建…

ARMv8.x-M架构计算能力概览

1.ARMv8.xM架构提供了哪些计算能力&#xff1f; ARMv7-M时代&#xff0c;Cortex-M系列CPU以提供通用计算能力为主。ARMv8-M架构提供了更加多样的计算能力。 首先&#xff0c;提供Thumb2指令集提供整数通用计算能力。 其次&#xff0c;ARMv8.x-M架构手册明确列出了更多可选的CPU…

20. Excel 自动化:Excel 对象模型

一 Excel 对象模型是什么 Excel对象模型是Excel图形用户界面的层次结构表示&#xff0c;它允许开发者通过编程来操作Excel的各种组件&#xff0c;如工作簿、工作表、单元格等。 xlwings 是一个Python库&#xff0c;它允许Python脚本与Excel进行交互。与一些其他Python库&#x…

大模型GGUF和LLaMA的区别

GGUF&#xff08;Gigabyte-Graded Unified Format&#xff09;和LLaMA&#xff08;Large Language Model Meta AI&#xff09;是两个不同层面的概念&#xff0c;分别属于大模型技术栈中的不同环节。它们的核心区别在于定位和功能&#xff1a; 1. LLaMA&#xff08;Meta的大语言…

一周学会Flask3 Python Web开发-SQLAlchemy查询所有数据操作-班级模块

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 我们来新建一个的蓝图模块-班级模块&#xff0c;后面可以和学生模块&#xff0c;实现一对多的数据库操作。 blueprint下新建g…

STM32学习【5】用按键控制LED亮灭(寄存器)以及对位运算的思考

目录 1. 看原理图2 使能GPIOAGPIOA时钟模块2.2 设置引脚GPIO输入2.3 读取引脚值 3. 关于寄存器操作的思考 写在前面 注意&#xff0c;这篇文章虽然说是用按键控制led亮灭&#xff0c;重点不在代码&#xff0c;而是关键核心的描述。 用寄存器的方式&#xff0c;通过key来控制led…

js,html,css,vuejs手搓级联单选

<!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>级联选择器</title><script src"h…

【Spring】第四弹:基于XML文件注入Bean对象

一、setter 注入Bean对象 1.创建Student对象 public class Student {private Integer id;private String name;private Integer age;private String sex;public Student() {}public Integer getId() {return id;}public void setId(Integer id) {this.id id;}public String …

DeepSeek私有化部署与安装浏览器插件内网穿透远程访问实战

文章目录 前言1. 本地部署OllamaDeepSeek2. Page Assist浏览器插件安装与配置3. 简单使用演示4. 远程调用大模型5. 安装内网穿透6. 配置固定公网地址 前言 最近&#xff0c;国产AI大模型Deepseek成了网红爆款&#xff0c;大家纷纷想体验它的魅力。但随着热度的攀升&#xff0c…

单目3d detection算法记录

1、centernet object as points 这篇文章的核心单目3d检测主要是利用中心点直接回归出3d模型的所有属性&#xff0c;head共享整个backbone&#xff0c;其中3d属性包括&#xff1a;2d目标中心点、2dw和h、2d offsets、3doffsets、3d dimmession、rot还有depth。 其中对应的dep…

MySQL程序

博主主页: 码农派大星. 数据结构专栏:Java数据结构 数据库专栏:数据库 JavaEE专栏:JavaEE 软件测试专栏:软件测试 关注博主带你了解更多知识 1. mysqld (MySQL服务器) mysqld也被称为MySQL服务器&#xff0c;是⼀个多线程程序&#xff0c;对数据⽬录进⾏访问管理(包含数据库…

rust学习笔记17-异常处理

今天聊聊rust中异常错误处理 1. 基础类型&#xff1a;Result 和 Option&#xff0c;之前判断空指针就用到过 Option<T> 用途&#xff1a;表示值可能存在&#xff08;Some(T)&#xff09;或不存在&#xff08;None&#xff09;&#xff0c;适用于无需错误信息的场景。 f…

IIS 服务器日志和性能监控

Internet Information Services &#xff08;IIS&#xff09; 是 Microsoft 提供的一款功能强大、灵活且可扩展的 Web 服务器&#xff0c;用于托管网站、服务和应用程序。IIS 支持 HTTP、HTTPS、FTP、SMTP 和更多用于提供网页的协议&#xff0c;因此广泛用于企业环境。 IIS 的…

基于Netty实现高性能HTTP反向代理

以下将分步骤实现一个基于Netty的高性能HTTP反向代理&#xff0c;支持动态路由、负载均衡和基础鉴权功能。 1. 项目依赖配置&#xff08;Maven&#xff09; 2. 定义路由规则 3. 实现HTTP反向代理服务端 4. 实现反向代理处理器 5. 实现基础鉴权 6. 性能优化策略 连接池管理…

Feedback-Guided Autonomous Driving

Feedback-Guided Autonomous Driving idea 问题设定&#xff1a;基于 CARLA 的目标驱动导航任务&#xff0c;通过知识蒸馏&#xff0c;利用特权智能体的丰富监督信息训练学生传感器运动策略函数 基于 LLM 的端到端驱动模型&#xff1a;采用 LLaVA 架构并添加航点预测头&#…

OpenCV基础【图像和视频的加载与显示】

目录 一.创建一个窗口&#xff0c;显示图片 二.显示摄像头/多媒体文件 三.把摄像头录取到的视频存储在本地 四.鼠标回调事件 五.TrackBar滑动条 一.创建一个窗口&#xff0c;显示图片 import cv2img_path "src/fengjing.jpg" # 自己的图片路径 img cv2.imre…

springboot实现调用百度ocr实现身份识别

一、技术选型 OCR服务&#xff1a;推荐使用百度AI 二、实现 1.注册一个服务 百度智能云控制台https://console.bce.baidu.com/ai-engine/ocr/overview/index?_1742309417611 填写完之后可以获取到app-id、apiKey、SecretKey这三个后面文件配置会用到 2、导入依赖 <!-- …

Linux--内核进程O(1)调度队列

⼀个CPU拥有⼀个runqueue 如果有多个CPU就要考虑进程个数的负载均衡问题 优先级 普通优先级&#xff1a;100〜139&#xff08;我们都是普通的优先级&#xff0c;想想nice值的取值范围&#xff0c;可与之对应&#xff01;&#xff09;实时优先级&#xff1a;0〜99&#xff08…