Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

内存管理 / Memory Management

1. C++ 内存管理总览:先看完整地图 / C++ Memory Management Map

2. 程序内存布局:内存不是只有堆和栈 / Program Memory Layout

3. 栈与函数调用:局部变量为什么会消失 / Stack and Function Calls

4. 堆与动态分配:new 到底做了什么 / Heap and Dynamic Allocation

5. 对象生命周期:地址还在不代表对象还活着 / Object Lifetime

6. RAII 与所有权:让对象负责清理 / RAII and Ownership Design

7. 智能指针与所有权关系:谁拥有,谁只是看 / Smart Pointers and Ownership Graphs

8. 容器、迭代器与内存失效 / Containers, Iterators, and Invalidation

9. allocator 与 PMR:把分配策略抽出来 / Allocators and PMR

10. 内存错误与 Sanitizer:把崩溃变成证据 / Memory Errors and Sanitizers

本页目录

程序内存布局:内存不是只有堆和栈 / Program Memory Layout ​

本文回答一个基础问题:内存和堆栈是什么关系?

简短说:内存是总概念,堆和栈只是进程地址空间里的两类常见区域。

1. 先把总图放出来 ​

程序运行起来以后,会成为一个进程。

进程拥有自己的虚拟地址空间。可以先把它理解成:程序运行时能看到的一整套地址世界。

简化图如下:

text
+--------------------------------------------------+
| text                                             |
| 函数编译后的机器指令                              |
+--------------------------------------------------+
| rodata                                           |
| 字符串字面量、只读常量                            |
+--------------------------------------------------+
| data                                             |
| 有初始值的全局变量、static 变量                   |
+--------------------------------------------------+
| bss                                              |
| 零初始化的全局变量、static 变量                   |
+--------------------------------------------------+
| heap                                             |
| 动态分配区域:new、malloc、容器扩容                |
+--------------------------------------------------+
| mmap / shared libraries                          |
| 动态库、内存映射文件、部分运行时区域               |
+--------------------------------------------------+
| stack                                            |
| 函数调用帧、局部自动变量、返回地址                 |
+--------------------------------------------------+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

真实系统会更复杂。

比如地址随机化、动态链接、线程栈、内存映射文件都会影响布局。

但学习 C++ 时,先抓住这件事:

text
同样叫“内存”,不同区域有不同用途和不同生命周期习惯。
1

2. 用代码给对象找位置 ​

看代码:

cpp
#include <string>
#include <vector>

int global_value = 10;
static int file_counter = 0;

int main() {
    int local = 42;
    static int local_static = 7;

    const char* text = "hello";
    std::string name = "Alice";
    std::vector<int> values{1, 2, 3};

    int* heap_int = new int(99);
    delete heap_int;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

逐个看:

text
global_value
  全局/静态区域

file_counter
  全局/静态区域,只是名字只在当前文件可见

local
  main() 这次调用的栈帧中

local_static
  全局/静态区域,不随 main() 栈帧销毁

"hello"
  通常在只读数据区域

text 这个指针变量
  main() 栈帧中

name 这个 string 对象本体
  main() 栈帧中

name 内部字符资源
  可能在对象内部,也可能在动态存储区,取决于实现和字符串长度

values 这个 vector 对象本体
  main() 栈帧中

values 的元素缓冲区
  通常在动态存储区

heap_int 这个指针变量
  main() 栈帧中

new int(99) 创建的 int 对象
  动态存储区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

这里最重要的不是背答案,而是建立分层。

std::vector<int> values 这个对象本体可以在栈上。

但它内部管理的元素数组通常在堆上。

text
stack frame of main
+------------------------------+
| values object                |
|  begin ----+                 |
|  end   ----|----+            |
|  cap   ----|----|----+       |
+------------|----|----|-------+
             |    |    |
             v    v    v
heap       +------------------+
           | 1 | 2 | 3 | ...  |
           +------------------+
1
2
3
4
5
6
7
8
9
10
11
12

所以“这个变量在栈上还是堆上”有时问得太粗。

更准确的问题是:

text
对象本体在哪里?
对象内部是否拥有另一块资源?
那块资源在哪里?
1
2
3

3. 代码区:函数也占内存 ​

函数不是飘在空气里的。

函数体编译后会变成机器指令,放进程序的代码区域。

cpp
int add(int a, int b) {
    return a + b;
}
1
2
3

编译之后,大致是:

text
C++ source
  |
  v
compiler
  |
  v
machine instructions
  |
  v
text segment
1
2
3
4
5
6
7
8
9
10

函数指针保存的是函数入口地址:

cpp
int (*fp)(int, int) = &add;
1

fp 是一个变量。

它保存一个指向代码位置的地址。

但你不能 delete fp。

因为这段代码不是你用 new 申请的对象资源。

4. 只读数据区:字符串字面量在哪里 ​

看代码:

cpp
const char* p = "hello";
1

这里至少有两件东西:

text
p
  一个指针变量

"hello"
  一个字符串字面量
1
2
3
4
5

大致关系:

text
stack frame
+------------------+
| p -------------- |----+
+------------------+    |
                        v
rodata              +-------------+
                    | h e l l o 0 |
                    +-------------+
1
2
3
4
5
6
7
8

字符串字面量通常放在只读区域。

所以不要写:

cpp
char* p = (char*)"hello";
p[0] = 'H';
1
2

这不是“强转一下就能改”的问题。

你试图修改一段不应该修改的存储,结果可能崩溃,也可能表现怪异。

5. 全局对象和 static 对象 ​

全局变量和静态变量有静态存储期。

静态存储期可以先理解成:

text
对象通常在程序启动附近创建,在程序结束附近销毁。
1

例子:

cpp
int global_count = 0;

void tick() {
    static int local_count = 0;
    ++local_count;
}
1
2
3
4
5
6

local_count 的名字只在 tick() 里可见。

但它的对象不会在每次函数返回时销毁。

这里要分清:

text
作用域
  名字在哪里能被写出来

存储期
  对象的存储能维持多久

生命周期
  对象什么时候被构造,什么时候被析构
1
2
3
4
5
6
7
8

静态对象不是万能的。

它的问题包括:

text
初始化顺序可能复杂
全局状态让测试变难
多线程访问可能需要同步
析构顺序也可能影响程序结束阶段
1
2
3
4

6. 栈和堆在总图中的位置 ​

栈主要服务函数调用。

每次函数调用,通常会出现一个调用帧。

调用帧里可能有局部变量、返回地址、保存寄存器、临时空间。

堆主要服务动态分配。

运行时才知道大小、需要跨作用域存在、容器需要扩容,都会用到动态存储。

关系可以这样看:

text
function call stack
+------------------+
| frame of g       |
+------------------+
| frame of f       |
+------------------+
| frame of main    |
+------------------+
        |
        | pointers / owners may refer to
        v
heap
+------------------+
| dynamic objects  |
| vector buffers   |
| string buffers   |
+------------------+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

栈和堆不是互相隔绝的。

栈上的对象可以拥有堆资源。

堆上的对象内部也可以有指针指向别处。

全局对象也可以拥有堆资源。

所以不要把内存问题简化成“这是不是堆”。

7. 本篇总结 ​

内存和堆栈的关系是:

text
memory
  |
  +-- text:代码
  +-- rodata:只读数据
  +-- data/bss:全局和 static 数据
  +-- heap:动态存储
  +-- mmap:映射区域
  +-- stack:函数调用工作区
1
2
3
4
5
6
7
8

以后看到一个变量,按顺序问:

text
这个名字在哪里可见?
对象本体在哪里?
对象内部有没有另外拥有资源?
那块资源由谁释放?
对象什么时候构造和析构?
1
2
3
4
5

下一篇讲栈与函数调用。理解栈以后,“为什么不能返回局部变量地址”就不再只是背规则。

最后更新于:

Pager
上一篇1. C++ 内存管理总览:先看完整地图 / C++ Memory Management Map
下一篇3. 栈与函数调用:局部变量为什么会消失 / Stack and Function Calls

持续记录,持续成长

Copyright © Tidenflow