C#个人学习笔记之堆和栈的介绍--005
·
C# 堆与栈 超清晰笔记
一、核心一句话总结
栈(Stack):自动分配、自动释放,存值类型、引用类型的引用地址,读写速度快,空间小。
堆(Heap):手动管理(GC回收),存引用类型的实际数据,空间大,读写稍慢。
二、基础概念区分
1. 栈(线程栈)
- 归属:每个线程独立拥有一块栈内存,线程私有。
- 存储内容
- 所有值类型:
int、double、bool、struct、枚举 - 引用类型变量的内存地址(引用)
- 函数局部变量、形参、返回地址
- 所有值类型:
- 内存特点
- 大小固定、容量小
- 先进后出(栈结构)
- 生命周期随方法/代码块结束自动销毁,无需手动释放
- 分配速度极快
2. 堆(托管堆)
- 归属:整个进程共享,全局公共内存,由 CLR 托管。
- 存储内容
- 引用类型的真实对象数据:
class、string、数组、委托
- 引用类型的真实对象数据:
- 内存特点
- 空间巨大,可动态扩容
- 内存分配零散
- 不会随方法结束立刻释放,由
.NET GC(垃圾回收器)定时回收 - 分配速度慢于栈
三、C# 类型与堆/栈对应关系(重点)
1. 值类型 → 主要存栈
值类型关键字:struct
常见类型:byte、short、int、long、float、double、decimal、char、bool、自定义结构体
规则:
- 局部值类型变量:直接存在栈上
- 如果值类型嵌套在引用类型内部:会跟着引用类型一起存到堆中
2. 引用类型 → 地址在栈,数据在堆
引用类型关键字:class
常见类型:object、string、数组、自定义类、委托、接口
固定模型(必记):
栈内存:引用变量(存对象地址)
↓ 指向
堆内存:对象真实数据
四、代码示例 + 内存图解(最直观)
示例1:纯值类型(全在栈)
// 局部变量,int 是值类型
int a = 10;
int b = a;
b = 20;
内存分布
- 栈上开辟空间存
a = 10 b = a:值拷贝,栈上新空间存b = 10- 修改
b,互不影响 a
结论:值类型赋值 = 拷贝数据本身
示例2:引用类型(栈存地址,堆存数据)
// 自定义类(引用类型)
class Person
{
public string Name;
}
// 1. 在栈创建 p1 引用变量
Person p1 = new Person();
// 2. new 触发:在堆创建 Person 对象
p1.Name = "张三";
// 3. 引用赋值:只拷贝【内存地址】
Person p2 = p1;
p2.Name = "李四";
内存分布
- 栈:
p1→ 保存堆中对象的地址 - 堆:存放
Person实例 +Name字符串数据 p2 = p1:栈上拷贝地址,p1和p2指向堆中同一个对象- 修改
p2.Name,p1.Name同步改变
结论:引用类型赋值 = 拷贝内存地址,多个变量指向同一堆对象
示例3:值类型嵌套在类中(值类型进堆)
struct Point // 结构体(值类型)
{
public int X;
}
class Test // 类(引用类型)
{
public Point pt; // 值类型成员
}
Test t = new Test();
内存:
- 栈:变量
t(地址) - 堆:
Test对象 + 内部Point结构体
规则:值类型在引用类型内部 → 跟随宿主进入堆
五、栈 vs 堆 对比表(笔记整理用)
| 对比项 | 栈(Stack) | 堆(Heap) |
|---|---|---|
| 所有者 | 单个线程私有 | 整个进程共享 |
| 存储内容 | 值类型、引用地址 | 引用类型真实对象 |
| 分配速度 | 极快 | 较慢 |
| 空间大小 | 小、固定 | 大、动态 |
| 释放方式 | 方法结束自动释放 | 由 GC 垃圾回收 |
| 溢出问题 | 易出现栈溢出(StackOverflow) | 易出现内存泄漏 |
| 生命周期 | 短(随代码块) | 长(等待GC) |
六、常见面试/笔试题考点(CSDN 笔记加分项)
- string 是什么类型?存在哪里?
string是引用类型,地址在栈,字符串数据在堆。 - 结构体(struct) 和 类(class) 本质区别?
struct = 值类型(默认栈);class = 引用类型(栈地址+堆数据)。 - 为什么递归太深会报 StackOverflowException?
每一次递归调用都会在栈上开辟栈帧,栈空间有限,超出容量就栈溢出。 - GC 会回收栈吗?
不会。GC 只负责托管堆,栈由运行时自动清理。 - 数组存在哪里?
数组是引用类型:数组引用在栈,数组整体数据存在堆。
七、总结(精简版,放笔记末尾)
- 栈:快、自动、存地址+值类型,线程私有,用完即销毁。
- 堆:大、GC管理、存引用对象,全局共享,等待垃圾回收。
- 赋值区别:值类型拷贝值,引用类型拷贝地址。
- 特殊规则:值类型嵌套在类中,会存入堆。
堆和栈的内存流向示意图如下
结合上文内容,我分文字结构化示意图
补充:C# 堆&栈 内存流向示意图
一、通用内存模型总览(全局架构图)
==================== 线程栈 (Stack) ====================
[局部变量、形参、返回地址、引用类型的地址指针]
↓ 指针指向
==================== 托管堆 (Heap) ======================
[引用类型实例对象、对象内部所有成员数据]
========================================================
规则:
1. 栈:线程私有,方法执行完自动释放
2. 堆:进程共享,由 .NET GC 回收
二、场景1:纯值类型(int)内存流向
对应代码:
int a = 10;
int b = a;
b = 20;
内存示意图
【线程栈】
┌──────────┐
│ a: 10 │ 变量a,直接存储数值
├──────────┤
│ b: 10 │ 执行 b=a,完整拷贝一份数值
└──────────┘
无堆内存参与
流向说明
- 声明
int a = 10:栈中开辟空间,存入数值10 int b = a:栈中新开辟空间,拷贝数值,两个变量相互独立- 修改
b=20:仅修改栈中b的值,a不受影响
三、场景2:引用类型(自定义类)核心流向(重点)
对应代码:
class Person
{
public string Name;
}
Person p1 = new Person();
p1.Name = "张三";
Person p2 = p1;
p2.Name = "李四";
内存示意图
【线程栈】 【托管堆】
┌────────────┐ ┌────────────────────────┐
│ p1: 0x0001 │ ────────►│ Person 对象实例 │
├────────────┤ │ Name: "张三" → "李四" │
│ p2: 0x0001 │ ────────►└────────────────────────┘
└────────────┘
流向说明
Person p1:栈上创建引用变量p1,此时无指向new Person():在堆中创建实体对象,CLR 返回对象内存地址0x0001,赋值给栈中的p1p1.Name = "张三":通过栈上地址,修改堆中对象的成员Person p2 = p1:栈上创建p2,仅拷贝地址 0x0001,两个变量指向堆中同一个对象- 修改
p2.Name:本质修改堆内数据,因此p1.Name同步变化
四、场景3:值类型嵌套在引用类型中(结构体+类)
对应代码:
struct Point { public int X; }
class Test { public Point pt; }
Test t = new Test();
t.pt.X = 100;
内存示意图
【线程栈】 【托管堆】
┌────────────┐ ┌────────────────────────┐
│ t: 0x0002 │ ────────►│ Test 对象 │
└────────────┘ │ ┌────────────────┐ │
│ │ Point 结构体 │ │
│ │ X: 100 │ │
│ └────────────────┘ │
└────────────────────────┘
流向说明
- 栈中创建引用变量
t,存储堆对象地址0x0002 new Test()在堆中创建Test对象- 结构体
Point是值类型,但作为类的成员,会直接内嵌在堆的宿主对象中,不再单独存在于栈
五、场景4:数组(引用类型)内存流向
对应代码:
int[] arr = new int[3];
arr[0] = 1;
内存示意图
【线程栈】 【托管堆】
┌────────────┐ ┌────────────┬────────────┬────────────┐
│ arr:0x0003 │ ────────►│ 1 │ 0 │ 0 │
└────────────┘ │ 数组元素1 │ 数组元素2 │ 数组元素3 │
└────────────┴────────────┴────────────┘
流向说明
数组属于引用类型:
- 数组引用变量
arr存于栈,记录堆地址 - 数组的所有元素(哪怕是值类型
int)全部存储在托管堆中
补充:栈溢出场景示意图(面试考点)
递归调用会持续往栈中新增栈帧,栈空间固定,超出上限则报错 StackOverflowException
【线程栈 空间上限】
┌────────────┐ 第1层递归
├────────────┤ 第2层递归
├────────────┤ 第3层递归
├────────────┤ ... 持续叠加
├────────────┤ 栈空间已满 → StackOverflow 栈溢出
└────────────┘
更多推荐
所有评论(0)