一、C/C++ 程序的内存布局
程序运行时,内存分为几个区域:

二、各区域详解
1. 栈区
特点:
- 自动管理,编译器分配和释放
- 速度快
- 空间有限(通常几MB)
- 向下增长(高地址→低地址)
存放什么:
cpp
void func() {
int a = 10; // 栈上
double b = 3.14; // 栈上
char arr[100]; // 栈上
int* p = &a; // p 在栈上,指向栈上的 a
}
// 函数结束,a、b、arr、p 自动销毁
生命周期:栈区(Stack) 的生命周期与作用域(Scope) 严格绑定:变量在进入作用域时创建,在离开作用域时自动销毁 。这是C/C++ 中最简单、最安全的内存管理方式,无需手动 new/delete。
1.1验证栈向下增长:
函数调用时的栈增长:
cpp
#include <iostream>
void func2() {
int x = 100;
std::cout << "func2 中 x 的地址:" << &x << std::endl;
}
void func1() {
int y = 200;
std::cout << "func1 中 y 的地址:" << &y << std::endl;
func2();
}
int main() {
int z = 300;
std::cout << "main 中 z 的地址:" << &z << std::endl;
func1();
return 0;
}

结果:
|--------------|-------------|
| main 地址最高 | 栈底在高地址 |
| func1 地址更低 | 新栈帧在更低地址 |
| func2 地址最低 | 更深调用在更低地址 |
| 地址递减 | 栈向下增长 ✅ |
因为****栈向下增长,所以 栈顶在低地址,栈底在高地址
2. 堆区
特点:
- 手动管理(new/malloc 分配,delete/free 释放)
- 速度较慢(需要查找空闲内存)
- 空间大(可达几GB)
- 向上增长(低地址→高地址)
- 生命周期:手动
delete才销毁
存放什么:
cpp
void func() {
int* p1 = new int(10); // 堆上
int* p2 = (int*)malloc(4); // 堆上
int* arr = new int[1000]; // 堆上
delete p1;
free(p2);
delete[] arr;
}
// 注意:p1、p2、arr 本身在栈上,它们指向的内存在堆上
3. 全局/静态数据区
存放什么:
cpp
int global_var = 100; // 全局区
static int static_var = 200; // 静态区
void func() {
static int count = 0; // 静态区(只初始化一次)
count++;
}
生命周期: 程序开始到程序结束。
4. 常量区
存放什么:
cpp
const char* str = "hello"; // "hello" 在常量区
const int MAX = 100; // 可能被优化到常量区
// 注意:不能修改
// str[0] = 'H'; // ❌ 运行时错误
5. 代码段
存放什么:
cpp
void func() {
// 函数编译后的机器码存放在代码段
}
int main() {
// main 的机器码也在代码段
}
三、栈 vs 堆 对比
| 特性 | 栈 | 堆 |
|---|---|---|
| 管理方式 | 自动 | 手动 |
| 分配速度 | 快 | 慢 |
| 空间大小 | 小(几MB) | 大(几GB) |
| 生命周期 | 随作用域 | 手动控制 |
| 碎片问题 | 无 | 有 |
| 访问方式 | 直接 | 通过指针 |
四、详细示例:什么在栈,什么在堆
cpp
int global = 10; // 全局区
void func() {
int a = 1; // 栈
int b = 2; // 栈
int arr[10]; // 栈
int* p1 = new int(100); // p1 在栈,指向堆
int* p2 = new int[1000]; // p2 在栈,指向堆
static int s = 5; // 静态区
const char* str = "hello";// str 在栈,"hello"在常量区
delete p1;
delete[] p2;
}
五、函数栈帧详解
什么是栈帧?
栈帧 = 一个函数在栈上占用的内存块
每次调用函数,都会在栈上创建一个栈帧,函数返回时销毁
栈帧结构:

关键点:
- 局部变量在 ebp 的上方(高地址)
- 参数在 ebp 的下方(低地址)
- 返回地址在更下方
示例:函数调用过程:
cpp
int add(int a, int b) {
int c = a + b;
return c;
}
int main() {
int x = 1;
int y = 2;
int z = add(x, y);
return 0;
}
栈帧变化过程:
步骤1:程序启动
cpp
高地址 ↓
│
▼
┌─────────────────────┐
│ (空) │
│ │
│ │
│ │
│ │
└─────────────────────┘
低地址 ↑
步骤2:进入 main,创建 main 的栈帧
cpp
高地址 ↓
│
▼
════════════════════════════════════════════════════
┌─────────────────────────────────────────────┐
│ main 的栈帧 │ ← 高地址(先创建)
├─────────────────────────────────────────────┤
│ 局部变量: z = ? │ ← main.ebp + 12
├─────────────────────────────────────────────┤
│ 局部变量: y = 2 │ ← main.ebp + 8
├─────────────────────────────────────────────┤
│ 局部变量: x = 1 │ ← main.ebp + 4
├─────────────────────────────────────────────┤ ← 0x7FFFFFFF (main.ebp)
│ 保存的旧 ebp │ ← main.ebp + 0
├─────────────────────────────────────────────┤
│ 返回地址(返回到系统) │ ← main.ebp - 4
└─────────────────────────────────────────────┘
════════════════════════════════════════════════════
低地址 ↑
步骤3:准备调用 add(x, y)
调用前,压入参数和返回地址:
cpp
高地址 ↓
│
▼
════════════════════════════════════════════════════
┌─────────────────────────────────────────────┐
│ main 的栈帧 │
├─────────────────────────────────────────────┤
│ 局部变量: z = ? │
├─────────────────────────────────────────────┤
│ 局部变量: y = 2 │
├─────────────────────────────────────────────┤
│ 局部变量: x = 1 │
├─────────────────────────────────────────────┤
│ 保存的旧 ebp │
├─────────────────────────────────────────────┤
│ 返回地址(返回到系统) │
└─────────────────────────────────────────────┘
════════════════════════════════════════════════════
┌─────────────────────────────────────────────┐
│ 调用 add 的信息区 │ ← 中间区
├─────────────────────────────────────────────┤
│ 返回地址(main调用add后返回的位置) │ ← 压栈
├─────────────────────────────────────────────┤
│ 参数: b = 2(传递给add的第二个参数) │ ← 压栈
├─────────────────────────────────────────────┤
│ 参数: a = 1(传递给add的第一个参数) │ ← 压栈
└─────────────────────────────────────────────┘
════════════════════════════════════════════════════
低地址 ↑
注意:
- 参数从右到左压栈:先压 b,再压 a
- esp 向下移动(数值变小)
步骤4:进入 add,创建 add 的栈帧
cpp
高地址 ↓
│
▼
════════════════════════════════════════════════════
┌─────────────────────────────────────────────┐
│ main 的栈帧 │
├─────────────────────────────────────────────┤
│ 局部变量: z = ? │
├─────────────────────────────────────────────┤
│ 局部变量: y = 2 │
├─────────────────────────────────────────────┤
│ 局部变量: x = 1 │
├─────────────────────────────────────────────┤
│ 保存的旧 ebp │
├─────────────────────────────────────────────┤
│ 返回地址(返回到系统) │
└─────────────────────────────────────────────┘
════════════════════════════════════════════════════
┌─────────────────────────────────────────────┐
│ 调用 add 的信息区 │
├─────────────────────────────────────────────┤
│ 返回地址 │
├─────────────────────────────────────────────┤
│ 参数: b = 2 │
├─────────────────────────────────────────────┤
│ 参数: a = 1 │
└─────────────────────────────────────────────┘
════════════════════════════════════════════════════
┌─────────────────────────────────────────────┐
│ add 的栈帧 │
├─────────────────────────────────────────────┤
│ 保存的旧 ebp(指向main.ebp) │
├─────────────────────────────────────────────┤
│ 局部变量: c = 3 │ ← 计算完成
└─────────────────────────────────────────────┘
════════════════════════════════════════════════════
低地址 ↑
步骤6:add 返回
- 保存返回值
c = 3(通常放到 EAX 寄存器) - 销毁 add 的栈帧
- 恢复 main 的栈帧
- 根据"返回地址"跳回 main
cpp
高地址 ↓
│
▼
════════════════════════════════════════════════════
┌─────────────────────────────────────────────┐
│ main 的栈帧 │ ← 高地址
├─────────────────────────────────────────────┤
│ 局部变量: z = ? │ ← 等待接收返回值
├─────────────────────────────────────────────┤
│ 局部变量: y = 2 │
├─────────────────────────────────────────────┤
│ 局部变量: x = 1 │
├─────────────────────────────────────────────┤
│ 保存的旧 ebp │
├─────────────────────────────────────────────┤
│ 返回地址(返回到系统) │
└─────────────────────────────────────────────┘
════════════════════════════════════════════════════
低地址 ↑
注意:esp 向上移动(数值变大),add 的栈帧被销毁
步骤7:main 接收返回值
int z = add(x, y); // z = 3
cpp
高地址 ↓
│
▼
════════════════════════════════════════════════════
┌─────────────────────────────────────────────┐
│ main 的栈帧 │
├─────────────────────────────────────────────┤
│ 局部变量: z = 3 │ ← 接收返回值
├─────────────────────────────────────────────┤
│ 局部变量: y = 2 │
├─────────────────────────────────────────────┤
│ 局部变量: x = 1 │
├─────────────────────────────────────────────┤
│ 保存的旧 ebp │
├─────────────────────────────────────────────┤
│ 返回地址(返回到系统) │
└─────────────────────────────────────────────┘
════════════════════════════════════════════════════
低地址 ↑
步骤8:main 结束,程序退出
六、两个关键指针
ebp(栈帧基址指针)
- 固定指向当前栈帧的基址
- 用于定位局部变量和参数
- 通过"旧 ebp"形成调用链
esp(栈顶指针)
- 永远指向"最新"的内存位置
- 动态变化:
- 调用函数:esp 减小(向下移动)
- 返回函数:esp 增大(向上移动)
七、常见问题
问题1:栈溢出
cpp
void func() {
int arr[10000000]; // 栈上分配 40MB
// ❌ 栈溢出!栈通常只有几MB
}
// 正确做法:用堆
void func2() {
int* arr = new int[10000000]; // 堆上分配
delete[] arr;
}
问题2:返回局部变量地址
cpp
int* func() {
int a = 10;
return &a; // ❌ 危险!返回栈上局部变量的地址
}
// 函数结束,a 已销毁,返回的指针指向已释放的栈空间
int main() {
int* p = func();
*p = 20; // 未定义行为
}
问题3:内存泄漏
cpp
void func() {
int* p = new int(10);
// 忘记 delete
}
// 堆上的内存泄漏,直到程序结束才回收
// 正确做法
void func2() {
int* p = new int(10);
delete p;
}
八、堆内存管理详解
malloc / free
cpp
int* p = (int*)malloc(sizeof(int) * 10); // 分配 40 字节
// 使用 p
free(p); // 释放
new / delete
cpp
int* p1 = new int; // 分配一个 int
int* p2 = new int(10); // 分配并初始化为 10
int* arr = new int[10]; // 分配数组
delete p1;
delete p2;
delete[] arr; // 数组用 delete[]
数组用delete[ ]
new vs malloc
| 特性 | new | malloc |
|---|---|---|
| 是运算符还是函数 | 运算符 | 函数 |
| 返回类型 | 类型安全 | void*(c++需要强制类型转换) |
| 构造函数 | 会调用(创建对象会调用构造函数) | 不会调用 |
| 失败处理 | 抛异常 | 返回 NULL |
| 内存大小 | 自动计算 | 手动计算 |
九、总结图表
| 存储位置 | 内容 | 生命周期 |
|---|---|---|
| 栈 | 局部变量、函数参数 | 随作用域 |
| 堆 | new/malloc 分配的内存 | 手动控制 |
| 全局区 | 全局变量、静态变量 | 程序全程 |
| 常量区 | 字符串常量、const | 程序全程 |
| 代码段 | 程序机器码 | 程序全程 |