在C#中如何销毁一个结构体对象?

21

我有些困惑的是,在C#中只有引用类型才会被垃圾回收。 这意味着GC只选取引用类型进行内存释放。 那么值类型会怎样,因为它们也占据栈上的内存空间?

8个回答

31
首先,值类型变量是在堆栈还是堆上取决于它们所处的上下文 - 如果它们在引用类型中,则无论如何都会在堆上。 (您应该考虑自己到底有多关心堆栈/堆分离 - 正如Eric Lippert所写的那样,这主要是 实现细节。)
然而,基本上,当上下文被收回时,值类型内存会被回收 - 因此,当您从方法返回并弹出堆栈时,“回收”整个堆栈帧。 同样,如果值类型值实际上是对象的一部分,则当该对象被垃圾回收时,内存将被回收。
简短的答案是您不必担心它 :) (当然,这假设除了内存之外您没有其他事情需要担心 - 如果您有包含对需要释放的本机句柄的引用的结构体,则情况略有不同。)

感谢提供这篇全面的文章的参考。我发现Eric Lippert的全局指令非常符合整个C#的哲学:“只关注语义,我们会为您处理其余部分”。 - Charles HETIER

20

我对一个事实感到有些困惑,即在C#中仅有引用类型会被垃圾回收。

这不是一个事实。或者说,这个说法的真假取决于你所指的“被垃圾回收”。垃圾回收器当然会检查值类型进行回收;这些值类型可能仍然存活并持有一个引用类型:

struct S { public string str; }
...
S s = default(S); // local variable of value type
s.str = M(); 
当垃圾回收器运行时,它确实会查看变量 s,因为它需要确定 s.str 仍然存在。

我的建议是:要准确地说明“被垃圾回收”这个动词的意思。

GC仅选择引用类型进行内存释放。

同样,这不是事实。假设你有一个实例:

class C { int x; }

整数的内存将位于垃圾回收堆上,并且在C实例未被引用时被垃圾回收器回收。

为什么您认为只有引用类型的内存才由垃圾回收器释放是错误的?正确的说法是,由垃圾回收器分配的内存将由垃圾回收器进行释放,这我认为是非常合理的。 因为垃圾回收器分配了它,所以它也负责清理。

那么值类型会怎样,因为它们也在堆上占用内存?

对它们什么都不会发生。 对它们没有任何要求。 栈大小为100万字节。 栈的大小在线程启动时确定; 它从一百万字节开始,并在整个线程执行过程中保持不变。 栈上的内存既不会被创建也不会被销毁; 只有其内容被更改。


30
“栈内存既不被创建也不被销毁” - 我建议我们将其称为“利珀特的栈内存保存定律”。 :) - Greg D

8
这个问题使用了太多动词,如destroyed、reclaimed、deallocated、removed。这与实际发生的情况不符。一个本地变量只是停止存在,就像挪威鹦鹉风格视频中所示。
一个方法有一个入口点,首先发生的是CPU堆栈指针被调整。创建一个“堆栈帧”,用于存储局部变量。CLR保证这个空间被初始化为0,这在C#中不是强制要求的特性,因为有明确赋值规则。
一个方法只有一个出口点,即使你的方法代码中充斥着多个return语句。此时,堆栈指针被恢复到其原始值。实际上,它“忘记”了本地变量的存在。它们的值没有以任何方式被“擦除”,字节仍然存在。但它们不会持续很久,你程序中的下一个调用将再次覆盖它们。CLR的零初始化规则确保你永远无法观察到那些旧值,否则会存在安全隐患。
非常快速,只需要一个处理器周期。在C#语言中,这种行为的一个可见副作用是值类型不能有终结器。确保不需要额外的工作。

这个本地变量不再存在了!它已经停止存在了!它是一个已经不存在的变量。 :) - Dave Jellison

4

当值类型在作用域之外时,从堆栈中移除。


3

值类型在超出其作用域时立即被销毁。


0

值类型在执行后,当堆栈帧被移除时会被释放,我想是这样的。


0
还想补充一点,栈是在线程级别上的,堆是在应用程序域级别上的。
因此,当一个线程结束时,它会回收该特定线程使用的栈内存。

0

.NET 中的每个值类型实例都将成为其他东西的一部分,这可能是一个更大的封闭值类型实例、堆对象或堆栈帧。无论何时,任何这些东西出现时,其中的任何结构也将随之出现;只要包含它们的东西存在,这些结构就会继续存在。当包含结构的东西停止存在时,结构也将停止存在。没有办法摧毁一个结构而不摧毁容器,也没有办法摧毁包含一个或多个结构的东西而不摧毁其中包含的结构。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接