一、先铺垫:程序运行时的 RAM 分区
CPU 运行程序时,操作系统会给进程分配一块物理 RAM,并虚拟划分为 5 大区域(地址从低到高):
- 代码段 (.text):存放编译好的程序二进制指令(只读)
- 全局 / 静态区 (.data/.bss):全局变量、static 静态变量,程序启动分配、结束释放
- 栈 Stack:高地址向下增长,自动管理内存
- 堆 Heap:低地址向上增长,手动管理内存
- 内核共享区:系统调用、动态库等(不用细管)
核心一句话:栈是系统自动分配释放的临时内存;堆是程序员手动申请释放的自由大块 RAM
二、栈 Stack(栈内存,在 RAM 高位)
1. 内存分配主体:操作系统自动管理,完全不用程序员操作
每调用一个函数,OS 就会在 RAM 栈区开辟一块栈帧,函数执行完立刻自动回收这块 RAM,无需手动释放。
2. 存储什么(全部临时数据)
- 函数局部变量、函数形参
- 函数返回地址、寄存器临时备份
- 常量数组、结构体(定义在函数内部,无 malloc)
3. RAM 栈核心特性
- 大小固定、容量极小栈的总大小编译 / 系统限定(Windows 默认 1MB,Linux 8MB),RAM 栈空间很小,不能存超大数组,否则栈溢出崩溃。
- 地址从高向低增长新函数栈帧占用更低地址,后进先出 LIFO 结构。
- 分配释放极快仅移动栈指针 SP 寄存器,只改一个地址值,不用操作系统内核介入,速度接近寄存器。
- 生命周期绑定函数函数执行完毕,这块 RAM 直接还给栈,变量立刻失效,数据销毁。
栈示例代码 (C 语言)
void func() { int a = 10; // a存RAM栈 char buf[1024]; // 数组全部存在栈RAM } int main(){ func(); // 调用func,开辟栈帧;函数结束,a、buf占用的RAM瞬间回收 return 0; }栈常见坑
void test(){ char big[1024*1024*10]; // 10MB数组,远超栈RAM上限 → 栈溢出程序崩溃 }三、堆 Heap(堆内存,RAM 中间自由区域)
1. 内存分配主体:程序员手动控制,操作系统仅提供 RAM 块
堆是 RAM 中独立、超大、自由的内存池,必须通过malloc/calloc/realloc向系统申请 RAM,用完必须free归还,否则永久占用 RAM(内存泄漏)。
2. 存储什么
- malloc/new 动态申请的数组、大缓冲区、动态结构体、链表节点
- 运行时才确定大小的数据(编译期不知道占用多少 RAM)
3. RAM 堆核心特性
- 容量巨大,几乎占用剩余全部空闲 RAM没有栈那种几 MB 硬性限制,适合存图片、大缓存、海量数据。
- 地址从低向高增长每次 malloc 找堆中空闲 RAM 块分配,内存地址不连续(会产生内存碎片)。
- 分配释放慢调用堆管理器、内核申请 RAM,涉及内存块查找、合并碎片,远慢于栈。
- 生命周期完全自主只要不 free,哪怕函数执行结束,这块堆 RAM 依然存在,全局可访问;手动 free 后才归还操作系统 RAM。
堆示例代码 (C 语言)
void func() { // 在堆RAM申请10KB内存,指针p存在栈,真正的数据存在堆 char *p = (char*)malloc(1024*10); if(p == NULL) return; // RAM不足分配失败 } int main(){ func(); // 函数func执行完,栈里的p变量销毁,但堆上10KB RAM没有释放 → 内存泄漏 return 0; }正确写法:用完必须 free,释放堆 RAM
free(p); // 把这块RAM交还给堆管理器 p = NULL;堆常见问题
- 内存泄漏:malloc 不 free,RAM 长期占用,程序越跑越卡
- 野指针:free 后继续使用指针,访问已归还的 RAM,程序崩溃
- 内存碎片:频繁 malloc/free,堆 RAM 被分割成大量小块,大块内存申请失败
四、RAM 视角:栈 vs 堆 直观对比表
| 对比维度 | 栈(Stack RAM) | 堆(Heap RAM) |
|---|---|---|
| RAM 位置 | 进程虚拟地址高位 | 全局区下方、栈下方中间区域 |
| 内存管理者 | 操作系统自动管理 | 程序员手动 malloc/free |
| 内存大小 | 极小,固定上限 (1M/8M) | 极大,可用整机空闲 RAM |
| 分配速度 | 极快(仅移动栈指针) | 较慢(堆管理器、系统调用) |
| 数据生命周期 | 函数结束自动释放 RAM | 手动 free 才释放,全局存活 |
| 存储数据 | 局部变量、小数组、形参 | 动态缓冲区、大数据、链表 / 结构体 |
| 内存连续性 | 栈帧连续,无碎片 | 频繁分配会产生 RAM 内存碎片 |
| 内存错误 | 栈溢出(超大局部数组) | 内存泄漏、野指针、堆破坏 |
五、经典示意图(RAM 地址从上到下)
高地址 ─────────────────────── 栈Stack ↓(向下增长) ↓ 空闲内存 ↑ 堆Heap ↑(向上增长) 全局/静态区 代码段 低地址 ───────────────────────六、一个完整例子,看懂同一段代码里两块 RAM 分工
#include <stdlib.h> void demo(){ // 1. num 变量本身存【栈RAM】,仅4字节 int num = 100; // 2. ptr指针存【栈RAM】(8字节地址) // malloc申请的1000字节数据本体,存在【堆RAM】 char *ptr = (char*)malloc(1000); // 使用堆内存 ptr[0] = 'A'; // 释放堆RAM,否则这块1000字节RAM永久占用 free(ptr); ptr = NULL; } int main(){ demo(); // demo函数结束:栈上num、ptr占用的RAM自动回收 // 若没写free,堆上1000字节RAM不会回收,造成泄漏 return 0; }关键点总结
- 所有不带 malloc 的局部变量,全部占用栈 RAM,系统自动清理;
malloc/new申请的数据本体,全部存堆 RAM,必须手动释放;- 存放堆地址的指针变量,依然存在栈 RAM 里。