cpp

内存基础知识总结

讲解内存相关的基础知识,虚拟内存,基础类型的内存大小,STL容器的内存大小等内容

Posted by CongYu on February 1, 2026

内存基础知识总结

1. 虚拟内存

  • 图示:内存模型
  • 参考链接:
    • https://zhuanlan.zhihu.com/p/348171413
    • https://zhuanlan.zhihu.com/p/82746153
    • https://zhuanlan.zhihu.com/p/495709005

1.1 核心概念与机制

  • 地址空间:一个进程的地址空间包含该进程所有相关内存(code / stack / heap 等)。从进程视角看,是一块从 0 到高地址的连续逻辑空间。
  • 早期方式:用基址寄存器+界限寄存器把每个进程的整块地址空间映射到物理内存
    • 存在以下问题:
      • 大小难以限定、不够用难扩展;
      • 进程内未使用区域浪费物理内存。
  • 虚拟内存:每个进程使用独立的逻辑地址空间,将逻辑地址空间按「页」划分;只有用到的页才占用物理内存或交换区,实现隔离与按需使用。

  • 页(Page):物理内存和进程地址空间都被分成大小相等的块,每块为一页,页内地址连续。进程也按页划分。
  • MMU(内存管理单元):位于 CPU 内,负责虚拟地址到物理地址的转换(查页表)。
  • 页表机制:多级页表、TLB(Translation Lookaside Buffer)加速地址转换。
  • Swap:进程中部分页可仅在磁盘交换区中,需要时再换入物理内存。
  • COW(Copy-on-Write):fork 后子进程与父进程先共享物理页,写时再复制,减少冗余占用。
  • 内存分配器:glibc malloc、tcmalloc、jemalloc 等(实现与性能对比见后文 malloc 小节)。

内存占用度量指标

  • RSS(Resident Set Size):进程实际占用的物理内存。
  • VSZ(Virtual Size):进程虚拟地址空间大小。
  • PSS(Proportional Set Size):按共享比例分摊后的物理内存(含共享库等)。
  • USS(Unique Set Size):进程独占的物理内存。
  • Swap:换出到磁盘的内存大小及使用情况。

1.2 进程逻辑内存布局

  • 内核空间(kernel):高地址端,含命令行参数、环境变量等。
  • 用户空间:
    • 栈(stack):函数参数、局部变量、调用上下文;高地址向低地址增长,向下增长。
    • 堆(heap):动态分配(如 new/delete);低地址向高地址增长,向上增长;堆和栈中间为可扩展的空闲区。
    • 数据区(全局区/静态区):
      • .bss:未初始化的全局变量、静态变量
      • .data:已初始化的全局变量、静态变量
      • .rodata:只读数据(常量、字面量、const、#define 等)
    • 代码区 .text:机器码、指令。
1
2
3
4
5
6
7
MyClass obj1(10);        // 栈上创建对象
MyClass* obj2 = new MyClass(20);  // 堆上创建对象

obj1.setData(30);
int value = obj1.getData();

delete obj2;  // 释放堆内存

1.3 malloc

  • bk
  • mmp

malloc分配规则

  • 分配内存 < M_MMAP_THRESHOLD,走__brk,从内存池获取,失败的话走brk系统调用
  • 分配内存 >= M_MMAP_THRESHOLD,走__mmap,直接调用mmap系统调用

2. 基础类型大小

  • 1字节 = 8位
  • 1Byte(字节) = 8Bit
  • 1KB (Kilobyte 千字节) = 1024Byte
  • 1MB (Megabyte,兆字节,简称“兆”) = 1024KB
  • 1GB (Gigabyte,吉字节,又称“千兆”) = 1024MB
  • 1TB (Terabyte,太字节,或百万兆字节) = 1024GB
  • 1024=2^10。
类型 大小(字节) 位数 取值范围
int8_t 1 8 -128 ~ 127
int16_t / short 2 16 -32768 ~ 32767
int32_t / int 4 32 -2^31 ~ 2^31-1
int64_t / long 8 64 -2^63 ~ 2^63-1
float 4 32 单精度
double 8 64 双精度
char 1 8 ASCII字符 0 ~ 255
char16_t 2 16 UTF-16字符
char32_t 4 32 UTF-32字符
bool 1 8 布尔值 {0, 1}
  • size_t
    • 32 位系统中,size_t 是4字节,uint32_t
    • 64 位系统中,size_t 是8字节
  • double与float的表示:阶码
    • 符号位 + 阶码 + 尾数
    • double 1 + 11 + 52
    • float 1 + 8 + 23
  • 大小端存储
    • 大端存储:字数据的高字节存储在低地址中
    • 小端存储:子数据的低字节存储在低地址中
  • sizeof的结果单位是字节
  • mutex 内存占用40字节
  • shared_ptr 内存占用16字节。是原始指针的两倍

3. STL容器对象大小

3.1 STL容器大小

容器类型 对象大小(字节) 内部结构
std::vector 24 指针(8) + size(8) + capacity(8)
std::deque 80 分段数组,结构复杂
std::list 24 头指针 + 尾指针 + size
std::map 48 红黑树
std::set 48 红黑树
std::string 32 SSO优化

3.2 Vector内存管理详解

3.2.1 Vector的内存布局

std::vector内存布局:

  • 栈上对象本身(24字节)
    • 指针(8字节):指向堆上的数据数组
    • size(8字节):当前元素数量
    • capacity(8字节):已分配的容量
  • 堆上数据(capacity × 元素大小)

总内存 = sizeof(vector对象) + capacity × sizeof(元素类型)

示例:

1
2
3
4
std::vector<int> vec{1,2,3,4,5,6,7};
栈内存:24字节
堆内存:7 × 4 = 28字节
总计:52字节

3.2.2 Vector扩容策略

当容量不足时,vector会自动扩容:

容量变化:0 → 1 → 2 → 4 → 8 → 16 → 32 …

扩容规则:

  • GCC:通常按2倍增长
  • MSVC:通常按1.5倍增长
  • 每次扩容都需要重新分配内存并拷贝数据

3.3 String的SSO优化

SSO(Small String Optimization)小字符串优化

string内部结构(32字节):

  • 指针(8字节):指向字符串数据
  • 长度(8字节):字符串长度
  • 本地缓冲区(16字节):用于存储短字符串
特性 短字符串(SSO) 长字符串(堆分配)
构造速度 快(约10ns) 慢(约100ns)
拷贝速度 快(约15ns) 慢(约100ns)
析构速度 快(约1ns) 慢(约50ns)
内存分配 0次 1次
缓存友好

SSO适用场景:

  • 短字符串:文件名、标签、ID等
  • 性能敏感:需要频繁创建/销毁字符串的场景
短字符串模式(≤15字符)
1
2
3
4
5
6
7
不分配堆内存,字符串直接存储在32字节的对象中
┌──────────┬──────────┬────────────────────────────────┐
│ 指针无效  │ 长度=5    │ "hello\0" + 未使用空间           │
│          │          │         16字节                  │
└──────────┴──────────┴────────────────────────────────┘
栈内存:32字节
堆内存:0字节
长字符串模式(>15字符)
1
2
3
4
5
6
7
8
9
10
分配堆内存存储字符串
┌──────────┬──────────┬────────────────────────────────┐
│ 堆地址    │ 长度=23   │         容量                   │
│          │          │                                │
└──────────┴──────────┴────────────────────────────────┘
    ↓
  堆内存:存储实际字符串"hello world hello world\0"

栈内存:32字节
堆内存:24字节

4. 内存对齐

4.1 内存对齐规则

对齐是为了提高CPU访问内存的效率。

两条规则

  1. 每个成员的偏移量为成员自身大小的整数倍对齐
    • char(1字节):可以从任意地址开始
    • short(2字节):地址必须是2的倍数
    • int(4字节):地址必须是4的倍数
    • double(8字节):地址必须是8的倍数
  2. 最后进行结构体整体对齐,为最大成员大小的整数倍

4.2 优化内存布局

优化原则:按大小排序成员,大的放前面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 不好的做法:浪费空间
struct BadLayout
{
    bool flag1;    // 1 字节
    double value;  // 8 字节(需要对齐)
    bool flag2;    // 1 字节
    // 总共:24 字节(包含 padding)
};

// 好的做法:紧凑布局
struct GoodLayout
{
    double value;  // 8 字节
    bool flag1;    // 1 字节
    bool flag2;    // 1 字节
    // 总共:16 字节(包含 padding)
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 不好的做法:浪费空间
struct Bad {
    char a;    // 1字节
    int b;     // 4字节
    char c;    // 1字节
};
// 实际大小:12字节(1 + 3padding + 4 + 1 + 3padding)

// 好的做法:紧凑布局
struct Good {
    int b;     // 4字节
    char a;    // 1字节
    char c;    // 1字节
    // 自动填充2字节
};
// 实际大小:8字节(4 + 1 + 1 + 2padding)

4.3 AOS v.s. SOA

https://zhuanlan.zhihu.com/p/552884861

数据排布方式

  • AOS(Array of Structures)
  • SOA(Struct of Array)

AOS 更适合面向对象的编程,SOA更适用于需要效率更高的地方

5. 复杂对象的内存

5.1 嵌套容器

原始结构体:

1
2
3
4
5
6
struct ComplexObj {
    int a;                       // 4字节
    double b;                    // 8字节
    std::vector<int> inner_vec;  // 24字节(对象本身)
    std::string str;             // 32字节(对象本身)
};

内存层次:

1
2
3
4
5
6
栈上:
  ComplexObj对象:72字节
  
堆上(如果容器非空):
  inner_vec的元素:capacity × 4字节
  str的字符(如果>15字符):length + 1字节

实际内存布局:

1
2
3
4
5
6
7
8
9
偏移量    成员           大小    说明
----------------------------------------------
0-3       int a          4      
4-7       [padding]      4      填充,为了对齐double
8-15      double b       8      必须从8的倍数开始
16-39     vector         24     
40-71     string         32     
----------------------------------------------
总计:72字节

计算过程:

  • int a:4字节(偏移0-3)
  • padding:4字节(偏移4-7,为了让double从8开始)
  • double b:8字节(偏移8-15)
  • vector:24字节(偏移16-39)
  • string:32字节(偏移40-71)
  • 总计:4 + 4 + 8 + 24 + 32 = 72字节

5.2 Vector存储复杂对象

1
2
std::vector<ComplexObj> vec;
vec.resize(10);

内存占用:

1
2
3
4
5
6
7
8
9
栈上:
  vec对象:24字节
  
堆上:
  10个ComplexObj对象:720字节(72×10)
  
额外堆内存(如果对象内部容器非空):
  每个inner_vec的元素
  每个str的字符(如果>15字符)