文章目录
- 1. 虚拟内存空间
- 2. malloc和free
- 3. new和delete
- 4. 内存池
1. 虚拟内存空间
程序进程的虚拟内存空间是操作系统为每个进程提供的独立、连续的逻辑地址空间,与物理内存解耦。其核心目的是隔离进程、简化内存管理,并提供灵活的内存访问控制。
(图片取自于:【C++】内存管理分布_c++内存分布-CSDN博客)
自下而上为低地址到高地址。
以Linux x86-64架构为例:
虚拟内存空间分为用户空间和内核空间
- 用户空间(低地址 → 高地址):进程可直接访问的区域,通常占据虚拟地址空间的大部分。
- 内核空间(高地址保留区):操作系统内核专用,进程无法直接访问,需通过系统调用交互。
用户空间分为:
-
代码段(Text Segment)
- 地址范围:
0x400000
附近(起始地址)。 - 内容:编译后的可执行机器指令(如程序的
main
函数)。 - 权限:只读(Read-Only)、可执行(Execute),不可修改。
- 特点:多个进程可共享同一代码段(如动态库)。
- 地址范围:
-
初始化数据段(Data Segment)
- 地址范围:紧邻代码段。
- 内容:全局变量、静态变量(已显式初始化,如
int a = 10;
)。 - 权限:可读写(Read-Write),不可执行。
-
未初始化数据段(BSS Segment)
- 地址范围:紧邻初始化数据段。
- 内容:未显式初始化的全局/静态变量(如
int b;
),默认值为0。 - 权限:可读写,不可执行。
-
堆(Heap)
- 地址范围:向高地址动态增长。
- 内容:动态分配的内存(如
malloc()
、new
)。 - 权限:可读写,不可执行。
- 管理:由程序员控制分配/释放,可能产生内存碎片。
-
内存映射区域(Memory Mapping Segment)
-
地址范围:堆与栈之间的动态区域。
-
内容:
- 动态链接库(如
libc.so
)。 - 文件映射(
mmap
系统调用,如加载共享内存或大文件)。 - 匿名映射(用于大块内存分配,如某些
malloc
实现)。
- 动态链接库(如
-
权限:可自定义(读/写/执行)。
-
-
栈(Stack)
-
地址范围:用户空间顶端附近,向低地址增长。
-
内容:
- 函数调用栈帧(局部变量、函数参数、返回地址)。
- 线程栈(每个线程有独立栈空间)。
-
权限:可读写,不可执行。
-
管理:自动分配/释放,由编译器控制,大小有限(可能栈溢出)。
-
-
环境变量与命令行参数
- 地址范围:栈的顶部区域。
- 内容:
argv
(命令行参数)、envp
(环境变量)的字符串数组。 - 权限:可读写。
内核空间
- 地址范围:64位系统中通常为高地址的
0xffff800000000000
以上。 - 内容:
- 内核代码、数据结构。
- 进程页表、硬件驱动、中断处理程序等。
- 权限:仅内核态可访问,用户态访问会触发段错误。
2. malloc和free
参考文章:malloc 底层实现及原理_malloc底层原理-CSDN博客
malloc:
- 当分配的大小小于128KB时,通过
brk()
系统调用从堆段分配内存 - 当分配的大小大于等于128KB时,通过
mmap()
系统调用从文件映射段分配内存
注意:这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
free:
在实际分配内存时会多分配16字节(位于元数据之前)用来记录内存块描述信息,其中包括内存块的大小。
当调用free
释放内存时,传入的指针会首先向前偏移16字节,获取内存块的信息,然后再释放。
- 对于
brk()
申请的小空间,会标记为空间,并不会直接释放。当连续空闲空间大于128KB时会执行内存紧缩操作(trim) - 对于
mmap()
申请的大空间,会调用munmap()
归还给操作系统
3. new和delete
参考文章:【C++】内存管理分布_c++内存分布-CSDN博客
new
的底层步骤
- 内存分配:调用
operator new
函数(内部通常基于malloc
)申请指定大小的内存。 - 对象构造:在分配的内存上调用对象的构造函数(初始化成员变量等)。
- 异常处理:若内存不足,
operator new
抛出std::bad_alloc
异常(除非使用nothrow
版本)。
底层展开:
void* ptr = operator new(sizeof(MyClass)); // 内部调用 malloc
MyClass::MyClass(ptr); // 构造函数
delete
的底层步骤
- 对象析构:调用对象的析构函数(清理资源,如释放句柄)。
- 内存释放:调用
operator delete
函数(内部通常基于free
)释放内存。
底层展开:
MyClass::~MyClass(obj); // 析构函数
operator delete(obj); // 内部调用 free
与malloc
和free
的对比
特性 | new /delete | malloc /free |
---|---|---|
语言层面 | C++ 运算符,支持重载 | C 标准库函数,不可重载 |
内存初始化 | 调用构造函数/析构函数 | 仅分配/释放原始内存,无初始化逻辑 |
类型安全 | 返回类型明确指针(如 MyClass* ) | 返回 void* ,需手动类型转换 |
内存大小计算 | 自动计算类型所需大小(如 new int ) | 需手动指定字节数(如 malloc(4) ) |
错误处理 | 内存不足时抛出异常(可捕获) | 返回 NULL ,需检查返回值 |
数组支持 | 支持 new[] 和 delete[] | 需手动计算数组大小,无内置支持 |
底层扩展性 | 可自定义 operator new /operator delete | 无法修改 malloc /free 行为 |
适用场景 | C++ 对象管理(含构造/析构) | 原始内存操作或与 C 代码交互 |
异常与错误处理
-
new
:失败时抛出std::bad_alloc
,可通过try-catch
捕获。try {int* arr = new int[1000000000000]; } catch (const std::bad_alloc& e) {std::cerr << "内存不足: " << e.what() << std::endl; }
-
malloc
:失败时返回NULL
,需显式检查。int* arr = (int*)malloc(1000000000000 * sizeof(int)); if (arr == nullptr) {perror("malloc 失败"); }
内存对齐与重载
-
new
:支持自定义内存对齐(C++17 的align_val_t
)和重载。// 自定义 operator new void* operator new(size_t size, const char* tag) {std::cout << "通过标签分配: " << tag << std::endl;return malloc(size); } MyClass* obj = new ("DEBUG") MyClass();
-
malloc
:对齐由实现决定,无法直接定制。
数组处理
-
new[]
:自动计算数组元素总大小,并为每个元素调用构造函数。MyClass* arr = new MyClass[5]; // 调用 5 次构造函数 delete[] arr; // 调用 5 次析构函数
-
malloc
:需手动计算总字节数,且不构造对象。MyClass* arr = (MyClass*)malloc(5 * sizeof(MyClass)); free(arr); // 不会调用析构函数,可能导致资源泄漏
4. 内存池
内存池(Memory Pool)是一种预先分配并统一管理内存资源的技术,旨在优化动态内存分配的效率和性能。
4.1 基本概念
- 预先分配:在程序初始化阶段,一次性向操作系统申请一大块连续内存(称为“池”)。
- 自主管理:程序自行管理池内的内存分配与回收,避免频繁调用系统级函数(如
malloc
/free
)。 - 按需分配:从池中划分小块内存供程序使用,释放时标记为可复用而非立即归还操作系统。
4.2 核心优势
特性 | 传统malloc /free | 内存池 |
---|---|---|
分配速度 | 需遍历复杂数据结构(如Bins) | 直接定位空闲块,速度极快 |
内存碎片 | 易产生外部/内部碎片 | 碎片可控,甚至完全消除(固定块) |
系统调用开销 | 频繁调用brk /mmap | 仅初始化和销毁时调用 |
线程安全 | 全局锁可能引发竞争 | 可设计为线程私有池或无锁结构 |
适用场景 | 通用、动态需求 | 高频次、固定/可预测内存需求 |
4.3 实现方式
固定大小内存池
-
机制:池中所有内存块大小相同(如4KB)。
-
管理:使用空闲链表(Free List)跟踪可用块。
struct MemoryBlock {MemoryBlock* next; // 指向下一个空闲块 }; MemoryBlock* free_list; // 空闲链表头
-
操作:
- 分配:从链表头部取一个块,时间复杂度O(1)。
- 释放:将块插回链表头部,时间复杂度O(1)。
-
优点:零碎片、极快分配。
-
缺点:无法处理变长需求,可能浪费内存。
可变大小内存池
- 机制:支持不同大小的内存请求。
- 管理:
- 分离空闲链表:为不同大小范围维护多个链表(如8B、16B、32B…)。
- 伙伴系统(Buddy System):按2的幂次分割内存块,合并相邻空闲块。
- 分配时向上取整到最近的2^n大小。
- 释放时检查“伙伴块”是否空闲,若空闲则合并。
- 优点:灵活支持变长请求,减少碎片。
- 缺点:管理复杂度高,存在内部碎片。
4.4 典型应用场景
-
高频次小对象分配
- 如网络服务器为每个请求分配临时缓冲区。
- 示例:Nginx使用内存池管理HTTP请求资源。
-
实时系统
确保内存分配时间确定性,避免传统
malloc
的不可预测延迟。 -
游戏开发
快速创建/销毁大量游戏实体(如子弹、粒子效果),通过对象池(Object Pool)实现。
-
嵌入式系统
资源受限环境,需严格控制内存使用和碎片。
4.5 示例
#define BLOCK_SIZE 64 // 固定块大小
#define POOL_SIZE 100 // 池中块数量typedef struct MemoryBlock {struct MemoryBlock* next;
} MemoryBlock;MemoryBlock* free_list = NULL;// 初始化内存池
void init_pool() {static char pool[BLOCK_SIZE * POOL_SIZE];for (int i = 0; i < POOL_SIZE; i++) {MemoryBlock* block = (MemoryBlock*)(pool + i * BLOCK_SIZE);block->next = free_list;free_list = block;}
}// 分配内存
void* pool_alloc() {if (!free_list) return NULL; // 池耗尽MemoryBlock* block = free_list;free_list = free_list->next;return (void*)block;
}// 释放内存
void pool_free(void* ptr) {MemoryBlock* block = (MemoryBlock*)ptr;block->next = free_list;free_list = block;
}
(注:以上内容参考自DeepSeek)