C# 值类型 / 引用类型 内存布局(栈、堆、托管堆)

这是 C# 最核心的底层知识点之一,直接决定了变量赋值、方法传参、GC 垃圾回收的行为

先记住 3 个核心概念

1.栈 (Stack)

  • 线程独有,自动分配、自动释放(离开作用域立刻销毁)
  • 速度极快,内存连续
  • 只存:值类型变量、引用类型的引用地址(指针)

2.托管堆 (Managed Heap)

  • 进程共享,由 GC(垃圾回收器)自动管理
  • 速度较慢,内存不连续
  • 只存:引用类型的实际对象数据

**3.**值类型 / 引用类型

  • 值类型structenum、基本类型(int/float/bool/DateTime 等)
  • 引用类型classstring、数组、委托、接口等

一、内存布局总规则

类型 变量本身存在哪里? 实际数据存在哪里?
值类型 栈(或嵌套在引用对象里) 变量自己身上(和变量同内存)
引用类型 栈(存一个内存地址 / 指针) 托管堆

一句话总结:

  • 值类型 = 数据直接存在栈上
  • 引用类型 = 栈上存地址,堆上存真实数据

二、值类型内存布局

代码示例

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

内存:

  • s1s2 一开始指向同一个堆字符串
  • 重新赋值时,会在堆上创建新字符串,不再指向旧数据
  • 表现上像值类型,但底层依然是引用类型 + 托管堆

为什么字符串不能修改?

C# 设计 string 就是只读不可变的:

  • 你无法修改 "abc" 变成 "adc"

  • 任何 "修改" 字符串的操作(拼接、替换、赋值)都会创建新字符串,旧的不动

    cs 复制代码
    s1 = "a" + "b"; // 新建字符串
    s1 = s1.Replace("a","x"); // 新建字符串

    对比普通引用类型

  • 普通类(可修改)

    cs 复制代码
    Person p1 = new Person();
    p1.Name = "小明";
    
    Person p2 = p1;
    p2.Name = "小红"; // 会改 p1!因为指向同一个对象

    string(不可修改)

    cs 复制代码
    string s1 = "abc";
    string s2 = s1;
    s2 = "def"; // 不会改 s1!因为新建了对象
  • string 是引用类型

  • 存放在托管堆

  • 变量存的是地址

  • 但它不可变,不能修改内容

  • 赋值 = 换新地址,不是改旧内容

所以

cs 复制代码
s2 = "def";

不是修改 s2 指向的字符串,而是让 s2 指向了一个全新的字符串。

字符串 = 引用类型,但表现得像值类型 因为不可变,所以赋值不会互相影响。

六、快速判断内存位置

你可以直接用这个万能口诀:

  • 局部变量(方法内)

    • 值类型 →
    • 引用类型 → 栈存地址,堆存数据
  • 类的成员变量

    • 无论值 / 引用 → 全部存在堆上
  • 数组元素

    • 数组本身在堆 → 所有元素都在

七、值 / 引用类型核心区别

对比项 值类型 引用类型
存储位置 栈(或嵌入堆)
赋值行为 拷贝完整数据 只拷贝地址(指针)
内存管理 自动释放(栈弹出) GC 回收
空值 不能为 null 可以为 null
继承 继承自 System.ValueType 继承自 object

总结

  1. 栈 = 快、自动释放、存值类型 / 引用指针
  2. 托管堆 = 慢、GC 管理、存引用类型真实数据
  3. 值类型 = 数据自己存自己
  4. 引用类型 = 栈存地址,堆存数据
  5. class 里的值类型 → 存在堆上
相关推荐
蓝斯4972 小时前
C# 调用邮箱应用发送带附件的邮件
网络·c#·github
BuHuaX3 小时前
U3D抖微小游戏开发流程 (一)
unity·c#·游戏引擎·游戏策划
脚踏实地皮皮晨3 小时前
003003002_WPF Grid 基类官方类定义逐行深度解析
开发语言·windows·算法·c#·wpf·visual studio
凯丨14 小时前
AI 蠕虫来了:恶意文档如何通过 Copilot for Word 自我传播?
人工智能·c#·copilot
Aaron - Wistron14 小时前
Web API C# (Furion版)带 单元测试
开发语言·后端·c#
骊城英雄16 小时前
基于C#+avalonia ui实现的跨平台点胶机灌胶监控控制上位机软件
开发语言·ui·c#
吾儿良辰16 小时前
一个被BCL遗忘的高性能集合:C# CircularBuffer<T>深度解析
开发语言·windows·c#
哎呦喂我去去去16 小时前
C#实现屏幕墙:同时监控多个电脑桌面(支持Windows、信创Linux、银河麒麟、统信UOS)
linux·windows·c#
caishenzhibiao17 小时前
期货先行者主图 同花顺期货通指标
java·c语言·c#