栈用于存储值类型变量、方法参数等,由编译器自动管理;堆用于存储引用类型对象,由GC管理;ref struct禁止逃逸到堆;struct不一定在栈上,取决于声明位置;栈溢出不可捕获,堆内存不足会触发GC。

栈和堆在 C# 中的内存分配位置不同
栈(Stack)用于存储值类型变量、方法参数、局部变量和方法调用帧,由编译器自动管理,生命周期严格遵循后进先出(LIFO);堆(Heap)用于存储引用类型对象(如 class 实例、数组、字符串等),由 .NET 运行时的垃圾回收器(GC)管理,生命周期不固定。
关键区别不在“快慢”,而在“谁负责释放”:栈上内存随作用域退出自动弹出,无需 GC 干预;堆上对象只有在 GC 判定为不可达后才可能被回收,且时机不可控。
ref struct 为什么必须放在栈上
ref struct 是 C# 7.2 引入的特殊类型(如 Span、ReadOnlySpan),设计初衷就是禁止逃逸到堆——编译器会直接拒绝任何可能导致其被装箱、作为字段存入 class、或被捕获进 lambda 的写法。
常见报错:Cannot declare a variable of type 'Span,本质是编译器在做栈逃逸检查。
- 不能作为
class的字段 - 不能实现任何接口(接口变量会引发装箱)
- 不能用在
async方法中(因为状态机会生成堆上的状态机类)
值类型不一定都在栈上
很多人误以为 struct 总在栈上,其实只看“声明位置”:局部 struct 变量通常在栈上;但一旦它成为引用类型对象的字段(比如 class A { public Point p; }),那 p 就随 A 实例一起分配在堆上;同理,struct 数组元素也全部在堆上。
验证方式:用 unsafe + fixed 或 System.Runtime.CompilerServices.Unsafe.AsPointer 查看地址,你会发现同一 struct 类型在不同上下文里地址段完全不同。
95Shop可以免费下载使用,是一款仿醉品商城网店系统,内置SEO优化,具有模块丰富、管理简洁直观,操作易用等特点,系统功能完整,运行速度较快,采用ASP.NET(C#)技术开发,配合SQL Serve2000数据库存储数据,运行环境为微软ASP.NET 2.0。95Shop官方网站定期开发新功能和维护升级。可以放心使用! 安装运行方法 1、下载软件压缩包; 2、将下载的软件压缩包解压缩,得到we
unsafe
{
int x = 42;
Console.WriteLine($"stack addr: {(long)Unsafe.AsPointer(ref x)}"); // 通常高位地址(栈向下增长)
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 负担更重。









