这是 C# 最核心的底层知识点之一,直接决定了变量赋值、方法传参、GC 垃圾回收的行为。
先记住 3 个核心概念
1.栈 (Stack)
- 线程独有,自动分配、自动释放(离开作用域立刻销毁)
- 速度极快,内存连续
- 只存:值类型变量、引用类型的引用地址(指针)
2.托管堆 (Managed Heap)
- 进程共享,由 GC(垃圾回收器)自动管理
- 速度较慢,内存不连续
- 只存:引用类型的实际对象数据
**3.**值类型 / 引用类型
- 值类型 :
struct、enum、基本类型(int/float/bool/DateTime等) - 引用类型 :
class、string、数组、委托、接口等
一、内存布局总规则
| 类型 | 变量本身存在哪里? | 实际数据存在哪里? |
|---|---|---|
| 值类型 | 栈(或嵌套在引用对象里) | 变量自己身上(和变量同内存) |
| 引用类型 | 栈(存一个内存地址 / 指针) | 托管堆 |
一句话总结:
- 值类型 = 数据直接存在栈上
- 引用类型 = 栈上存地址,堆上存真实数据
二、值类型内存布局
代码示例
cs
// 值类型
int a = 10;
DateTime now = DateTime.Now;
内存布局
cs
【栈内存】
a: 10 ← 数据直接存在栈上
now: 时间值 ← 数据直接存在栈上
特点
1.变量离开作用域(如方法结束),栈自动弹出,内存立刻释放
**2.**赋值 = 完整拷贝一份数据
cs
int b = a; // b 是全新独立的 10,改 b 不影响 a
三、引用类型内存布局
代码示例
cs
// 引用类型
Person p = new Person();
p.Name = "小明";
p.Age = 20;
内存布局
cs
【栈内存】 【托管堆】
p: 0x123456 →→→ Person 对象
Name: "小明"
Age: 20
栈 :只存一个内存地址(指针)(64 位系统占 8 字节)
堆 :存真正的对象数据
特点
1.赋值 = 只拷贝地址,不拷贝数据
cs
Person p2 = p;
// p2 和 p 指向堆上同一个对象!改 p2.Age,p.Age 也会变
2.堆内存由 GC 自动回收(没有任何栈指针指向它时才会被回收)
四、混合场景:引用类型里包含值类型
cs
public class Person // 引用类型
{
public string Name; // 引用类型
public int Age; // 值类型
}
Person p = new Person();
p.Name = "小红";
p.Age = 18;
内存布局
cs
【栈】 【堆】
p: 0x123 →→ Person 对象
Name: 0x456 (指针) →→ 堆上字符串 "小红"
Age: 18 ← 值类型直接存在堆里!
结论
- 值类型如果被包在 class 里,它就存在堆上,不是栈!
- 只有独立的、局部的值类型变量才在栈上
五、string 特殊说明(引用类型,但表现像值类型)
string 是引用类型,但它是 ** 不可变(immutable)** 的。
cs
string s1 = "abc";
string s2 = s1;
s2 = "def"; // 不会修改 s1
内存:
s1和s2一开始指向同一个堆字符串- 重新赋值时,会在堆上创建新字符串,不再指向旧数据
- 表现上像值类型,但底层依然是引用类型 + 托管堆
为什么字符串不能修改?
C# 设计 string 就是只读不可变的:
-
你无法修改
"abc"变成"adc" -
任何 "修改" 字符串的操作(拼接、替换、赋值)都会创建新字符串,旧的不动
css1 = "a" + "b"; // 新建字符串 s1 = s1.Replace("a","x"); // 新建字符串对比普通引用类型
-
普通类(可修改)
csPerson p1 = new Person(); p1.Name = "小明"; Person p2 = p1; p2.Name = "小红"; // 会改 p1!因为指向同一个对象string(不可修改)
csstring s1 = "abc"; string s2 = s1; s2 = "def"; // 不会改 s1!因为新建了对象 -
string 是引用类型
-
存放在托管堆
-
变量存的是地址
-
但它不可变,不能修改内容
-
赋值 = 换新地址,不是改旧内容
所以
cs
s2 = "def";
不是修改 s2 指向的字符串,而是让 s2 指向了一个全新的字符串。
字符串 = 引用类型,但表现得像值类型 因为不可变,所以赋值不会互相影响。
六、快速判断内存位置
你可以直接用这个万能口诀:
-
局部变量(方法内)
- 值类型 → 栈
- 引用类型 → 栈存地址,堆存数据
-
类的成员变量
- 无论值 / 引用 → 全部存在堆上
-
数组元素
- 数组本身在堆 → 所有元素都在堆
七、值 / 引用类型核心区别
| 对比项 | 值类型 | 引用类型 |
|---|---|---|
| 存储位置 | 栈(或嵌入堆) | 堆 |
| 赋值行为 | 拷贝完整数据 | 只拷贝地址(指针) |
| 内存管理 | 自动释放(栈弹出) | GC 回收 |
| 空值 | 不能为 null | 可以为 null |
| 继承 | 继承自 System.ValueType |
继承自 object |
总结
- 栈 = 快、自动释放、存值类型 / 引用指针
- 托管堆 = 慢、GC 管理、存引用类型真实数据
- 值类型 = 数据自己存自己
- 引用类型 = 栈存地址,堆存数据
- class 里的值类型 → 存在堆上