c# 内存管理 栈和堆的区别

来源:这里教程网 时间:2026-02-21 17:36:36 作者:

栈和堆在 C# 中的内存分配位置不同

栈(Stack)用于存储值类型变量、方法参数、局部变量和方法调用帧,由编译器自动管理,生命周期严格遵循后进先出(LIFO);堆(Heap)用于存储引用类型对象(如

class
实例、数组、字符串等),由 .NET 运行时的垃圾回收器(GC)管理,生命周期不固定。

关键区别不在“快慢”,而在“谁负责释放”:栈上内存随作用域退出自动弹出,无需 GC 干预;堆上对象只有在 GC 判定为不可达后才可能被回收,且时机不可控。

ref struct
为什么必须放在栈上

ref struct
是 C# 7.2 引入的特殊类型(如
Span<t></t>
ReadOnlySpan<t></t>
),设计初衷就是禁止逃逸到堆——编译器会直接拒绝任何可能导致其被装箱、作为字段存入 class、或被捕获进 lambda 的写法。

常见报错:

Cannot declare a variable of type 'Span<int>' in a context where it may be lifted to an anonymous method or lambda expression</int>
,本质是编译器在做栈逃逸检查。

不能作为
class
的字段
不能实现任何接口(接口变量会引发装箱) 不能用在
async
方法中(因为状态机会生成堆上的状态机类)

值类型不一定都在栈上

很多人误以为

struct
总在栈上,其实只看“声明位置”:局部
struct
变量通常在栈上;但一旦它成为引用类型对象的字段(比如
class A { public Point p; }
),那
p
就随
A
实例一起分配在堆上;同理,
struct
数组元素也全部在堆上。

验证方式:用

unsafe
+
fixed
System.Runtime.CompilerServices.Unsafe.AsPointer
查看地址,你会发现同一
struct
类型在不同上下文里地址段完全不同。

unsafe
{
    int x = 42;
    Console.WriteLine($"stack addr: {(long)Unsafe.AsPointer(ref x)}"); // 通常高位地址(栈向下增长)
<pre class='brush:php;toolbar:false;'>var arr = new int[1];
Console.WriteLine($"heap addr: {(long)Unsafe.AsPointer(ref arr[0])}"); // 通常低位地址(堆向上增长)

}

GC 不管栈,但栈溢出照样崩

栈空间由操作系统在线程创建时分配(默认 Windows 是 1MB),深度递归、超大局部数组(如

int[1000000]
)、或无限嵌套的
async
状态机都可能触发
StackOverflowException
——这个异常无法 catch,进程直接终止。

而堆内存不足会抛

OutOfMemoryException
,此时 GC 会尝试回收,失败后才崩溃,还有调试窗口可抓内存快照。

避免在栈上分配大结构体(如含百万字节数组的
struct
递归算法优先改造成迭代,尤其处理树/图等深层结构时
stackalloc
分配的内存必须在当前作用域内使用,且不能返回给调用方指针

栈和堆不是性能高低的代名词,而是资源生命周期模型的选择。混淆它们最危险的地方,是以为“值类型=栈上=安全”,结果把大

struct
塞进 class 字段或集合里,既没省内存,又让 GC 负担更重。

相关推荐