面试题:堆 vs 栈 (C#) 前言 堆 vs 栈

2025-06-10

面试问题:堆与栈(C#)

前言

堆与栈

前言

最近我失业了,为了找新工作,这几周我频繁参加技术面试。尽管每个项目都各有不同,但面试官往往会不时地问一些相同的问题。我决定记录下我经常遇到的面试问题,并写下我的答案。

当你处于换工作状态时,准备面试可能会很乏味。所以我欢迎你关注这个系列,我会尝试在每篇文章中涵盖不同的面试主题。我会定期更新这些文章,希望对大家有所帮助。

注意:如果您发现错误,请在评论中告诉我,我会纠正。谢谢!

堆与栈


问:在 C# 中对象分配在哪里?

在 C# 中,有两个地方可以存储对象——堆和栈。

堆栈上分配的对象仅在堆栈框架(方法的执行)内部可用,而堆上分配的对象可以从任何地方访问。


问:哪些对象分配在栈上,哪些对象分配在堆上?

注意:你永远不应该说“引用类型在堆上分配,而值类型在堆栈上分配”,这是一个常见的错误,会给经验丰富的面试官敲响警钟。

引用类型(类、接口、委托)始终在堆上分配。

当你将一个引用对象作为参数传递或赋值给一个变量时,你实际上是在传递它的引用。引用(而不是被引用的对象)可以在栈或堆上分配。

通过传递对对象的引用,您可以知道该对象在堆上的位置,以便您的代码可以访问它。

每次将对象作为引用传递时,引用本身都会被复制。这意味着您可以将引用更改为指向其他对象,而不会影响先前的对象本身或指向该对象的其他引用。引用是轻量级的,并且始终是恒定大小(32 位或 64 位,具体取决于操作系统位数),因此复制它(并因此传递引用类型)被认为是廉价的。

值类型(派生自System.ValueType,例如int,,boolchar任何enumstruct可以在堆或堆栈上分配,具体取决于它们在何处声明。

  • 如果值类型在方法内部被声明为变量,那么它将存储在堆栈中。
  • 如果值类型被声明为方法参数,那么它将存储在堆栈中。
  • 如果值类型被声明为某个类的成员,那么它将与其父类一起存储在堆上。
  • 如果值类型被声明为结构的成员,那么它将存储在该结构存储的任何位置。

从 C#7.2 开始,astruct可以声明为ref struct,在这种情况下它将始终在堆栈上分配,从而防止在引用类型内声明它。

值类型的实例通过复制传递(除非使用引用语义,见下文)。这意味着每次将值类型赋值给变量或作为参数传递时,该值都会被复制。

由于复制值类型的成本可能会根据对象的大小而变得昂贵,因此不建议将占用大量内存的对象声明为值类型。

由于 C# 中的每个类型都派生自System.Object,因此值类型可以分配给变量或传递给需要 的方法object。在这种情况下,值会被复制并存储在堆上,包装成引用类型,这一操作称为装箱


问:我们可以使用具有引用语义的值类型吗?

诸如refand outref returnand ref local(C#7.0)、in(C#7.2) 等关键字允许通过引用访问值类型。这意味着,使用代码不会复制值,而是会接收该值的引用(无论该引用位于堆栈还是堆上),只要该值类型的生命周期比使用代码的生命周期长即可。


问:如何释放堆内存?

虽然在弹出包含堆栈的框架时,堆栈上存储的对象会消失,但堆上存储的对象所使用的内存需要由垃圾收集器释放

当存储在堆上的对象不再具有指向它的任何引用时,它就被认为有资格进行垃圾收集。

在某个时刻,垃圾收集器启动,中断所有正在运行的线程,调用它试图摆脱的对象的终结器(在特殊的终结器线程上),然后将内存标记为可自由使用。


问:在堆上分配和释放内存可能会出现什么问题?

随着堆内存的分配和释放,堆中会产生碎片。如下图所示:

HEAP:
---][-------][----------][-----]........
      obj 1      obj 2    obj 3   free
Enter fullscreen mode Exit fullscreen mode

obj 2被取消分配时,其内存将被释放:

HEAP:
---][-------]............[-----]........
      obj 1      free     obj 3   free
Enter fullscreen mode Exit fullscreen mode

现在,如果运行时需要在堆上分配另一个对象,它可以使用 释放的内存obj 2,但前提是新对象确实“适合”。如果该内存不足,运行时可能会通过扩展其工作集向操作系统请求更多连续内存,如下所示:

HEAP:
---][-------]............[-----][--------------------]...
      obj 1      free     obj 3         obj 4
Enter fullscreen mode Exit fullscreen mode

由于碎片化,内存使用效率会降低。为了解决这个问题,垃圾收集器可能会重新整理内存,使之不再有空隙。这可以通过简单地复制字节来实现,这一操作称为“碎片整理”。

HEAP:
---][-------][-----][--------------------]...............
      obj 1   obj 3         obj 4               free
Enter fullscreen mode Exit fullscreen mode

问:什么是大对象堆以及它有什么用途?

根据消耗的内存大小,内存碎片整理的成本可能很高,这就是为什么堆被进一步分为小对象堆(SOH)和大对象堆(LOH)。

如果对象小于 85k 字节,则存储在 SOH 中,否则存储在 LOH 中。85000 字节的临界点是根据经验设计的,超过该临界点后,碎片整理将不再提供性能优势。

由于 CPU 处理doubles 的方式,数组double是一个例外,如果数组中的元素超过 1000 个,则此类对象将存储在 LOH 上。

LOH 中的内存(通常)不会进行碎片整理,从而以较低内存使用效率为代价提供更好的性能。

鏂囩珷鏉ユ簮锛�https://dev.to/tyrrrz/interview-question-heap-vs-stack-c-5aae
PREV
单元测试被高估了
NEXT
面试题:async & await(C#)