【问题标题】:C# parameters by reference and .net garbage collection引用的 C# 参数和 .net 垃圾回收
【发布时间】:2011-01-30 20:19:08
【问题描述】:

我一直试图弄清楚 .NET 垃圾收集系统的复杂性,并且我有一个与 C# 引用参数相关的问题。如果我理解正确,方法中定义的变量存储在堆栈中,不受垃圾收集的影响。所以,在这个例子中:

public class Test
{
 public Test()
 {
 }

 public int DoIt()
 {
  int t = 7;
  Increment(ref t);
  return t;
 }

 private int Increment(ref int p)
 {
  p++;
 }
}

DoIt() 的返回值为 8。由于 t 的位置在堆栈上,因此该内存不能被垃圾收集或压缩,并且 Increment() 中的引用变量将始终指向 t 的正确内容.

但是,假设我们有:

public class Test
{
 private int t = 7;

 public Test()
 {
 }

 public int DoIt()
 {
  Increment(ref t);
  return t;
 }

 private int Increment(ref int p)
 {
  p++;
 }
}

现在,t 存储在堆中,因为它是我的类的特定实例的值。如果我将此值作为参考参数传递,这可能不是问题吗?如果我将 t 作为参考参数传递,p 将指向 t 的当前位置。但是,如果垃圾收集器在压缩期间移动了这个对象,那会不会弄乱 Increment() 中对 t 的引用?或者垃圾收集器是否会更新通过传递引用参数创建的引用?我有必要担心这个吗?唯一提到担心 MSDN 上的内存被压缩(我可以找到)是关于将托管引用传递给非托管代码。希望那是因为我不必担心托管代码中的任何托管引用。 :)

【问题讨论】:

  • 底线:规范承诺它会起作用,因此实现会让它起作用。

标签: c# pass-by-reference


【解决方案1】:

这正是你在最后一句中提到的方式。 GC 将在压缩堆时移动所有需要的引用(对非托管内存的引用除外)。

请注意,使用堆栈或堆与实例变量的值或引用类型有关。值类型(结构和“简单”类型,如 int、double 等)总是在堆栈中,类总是在堆中(堆栈中的内容是引用 - 指针 - 指向为实例分配的内存)。

编辑:正如评论中正确指出的那样,第二段写得太快了。如果一个值类型实例是一个类的成员,它不会存储在堆栈中,它会像其他成员一样存储在堆中。

【讨论】:

  • 第二段完全错误。正确的说法是“当值类型是本地或临时变量并且本地不是 lambda 的封闭外部变量或匿名方法,并且该块不是迭代器块。”
  • 感谢您的精确。我已经修改了答案。
【解决方案2】:

如果我理解正确,方法中定义的变量存储在堆栈中,不受垃圾回收的影响。

这取决于您所说的“受影响”是什么意思。堆栈上的变量是垃圾收集器的,所以它们肯定会影响垃圾收集。

由于 t 的位置在堆栈上,因此该内存不能被垃圾收集或压缩,并且 Increment() 中的引用变量将始终指向 t 的正确内容。

“不能”在这里用起来很奇怪。首先使用堆栈的目的是因为堆栈仅用于从不需要压缩并且其生命周期总是已知的数据,因此它永远不需要被垃圾收集。这就是我们首先使用堆栈的原因。你在这里似乎本末倒置。让我重复一遍以确保清楚:我们将这些东西存储在堆栈中的原因是因为它的生命周期是已知的,所以不需要收集或压缩它。如果不知道它的生命周期,那么它就会在堆上。例如,由于这个原因,迭代器块中的局部变量在堆上。

现在,t 存储在堆中,因为它是我的类的特定实例的值。

正确。

如果我将此值作为参考参数传递,这可能不是问题吗?

不。没关系。

如果我将 t 作为参考参数传递,p 将指向 t 的当前位置。

是的。虽然我更倾向于认为 p 是变量 t 的别名。

但是,如果垃圾收集器在压缩期间移动该对象,那会不会弄乱 Increment() 中对 t 的引用?

不。垃圾收集器知道托管引用;这就是为什么它们被称为托管引用。如果 gc 移动事物,托管引用仍然有效。

如果您使用不安全代码将实际的 指针 传递给 t,那么您将需要将 t 的容器 pin 固定到位,以便垃圾收集器知道不要移动它。您可以使用 C# 中的固定语句来执行此操作,或者通过为要固定的对象创建 GCHandle。

垃圾收集器是否会更新通过传递引用参数创建的引用?

是的。如果没有,它会相当脆弱。

我需要担心这个吗?

不。您正在像一个非托管 C++ 程序员一样思考这个问题——C++ 让您完成这项工作,但 C# 没有。请记住,托管内存模型的全部意义在于让您不必考虑这些东西。

当然,如果您喜欢担心这些问题,您可以随时使用“不安全”功能来关闭这些安全系统,然后您就可以随心所欲地编写破坏堆和堆栈的错误。

【讨论】:

    【解决方案3】:

    不,您不必担心。基本上,调用方法 (DoIt) 具有对 Test 实例的“实时”引用,这将防止它被垃圾收集。我不确定它是否可以被压缩 - 但我怀疑它可以,因为 GC 能够发现哪些变量引用是被移动对象的一部分。

    换句话说 - 不用担心。不管它是否可以被压缩,它都不应该给你带来问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-29
      • 2021-12-20
      • 1970-01-01
      • 1970-01-01
      • 2017-06-20
      • 1970-01-01
      • 2013-04-20
      • 1970-01-01
      相关资源
      最近更新 更多