C# 参数引用和 .net 垃圾回收

发布于 2024-08-25 20:27:26 字数 843 浏览 8 评论 0原文

我一直在试图弄清楚 .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(我可以找到)上唯一提到担心内存被压缩的问题是与将托管引用传递给非托管代码有关。希望这是因为我不必担心托管代码中的任何托管引用。 :)

I have been trying to figure out the intricacies of the .NET garbage collection system and I have a question related to C# reference parameters. If I understand correctly, variables defined in a method are stored on the stack and are not affected by garbage collection. So, in this example:

public class Test
{
 public Test()
 {
 }

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

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

the return value of DoIt() will be 8. Since the location of t is on the stack, then that memory cannot be garbage collected or compacted and the reference variable in Increment() will always point to the proper contents of t.

However, suppose we have:

public class Test
{
 private int t = 7;

 public Test()
 {
 }

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

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

Now, t is stored on the heap as it is a value of a specific instance of my class. Isn't this possibly a problem if I pass this value as a reference parameter? If I pass t as a reference parameter, p will point to the current location of t. However, if the garbage collector moves this object during a compact, won't that mess up the reference to t in Increment()? Or does the garbage collector update even references created by passing reference parameters? Do I have to worry about this at all? The only mention of worrying about memory being compacted on MSDN (that I can find) is in relation to passing managed references to unmanaged code. Hopefully that's because I don't have to worry about any managed references in managed code. :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

城歌 2024-09-01 20:27:26

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

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

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

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

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

正确的。

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

没有。没关系。

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

是的。尽管我更喜欢认为 p 是变量 t 的别名。

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

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

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

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

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

我需要担心这个吗?

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

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

If I understand correctly, variables defined in a method are stored on the stack and are not affected by garbage collection.

It depends on what you mean by "affected". The variables on the stack are the roots of the garbage collector, so they surely affect garbage collection.

Since the location of t is on the stack, then that memory cannot be garbage collected or compacted and the reference variable in Increment() will always point to the proper contents of t.

"Cannot" is a strange word to use here. The point of using the stack in the first place is because the stack is only used for data which never needs to be compacted and whose lifetime is always known so it never needs to be garbage collected. That why we use the stack in the first place. You seem to be putting the cart before the horse here. Let me repeat that to make sure it is clear: the reason we store this stuff on the stack is because it does not need to be collected or compacted because its lifetime is known. If its lifetime were not known then it would go on the heap. For example, local variables in iterator blocks go on the heap for that reason.

Now, t is stored on the heap as it is a value of a specific instance of my class.

Correct.

Isn't this possibly a problem if I pass this value as a reference parameter?

Nope. That's fine.

If I pass t as a reference parameter, p will point to the current location of t.

Yep. Though the way I prefer to think of it is that p is an alias for the variable t.

However, if the garbage collector moves this object during a compact, won't that mess up the reference to t in Increment()?

Nope. The garbage collector knows about managed references; that's why they're called managed references. If the gc moves the thing around, the managed reference is still valid.

If you had passed an actual pointer to t using unsafe code then you would be required to pin the container of t in place so that the garbage collector would know to not move it. You can do that using the fixed statement in C#, or by creating a GCHandle to the object you want to pin.

does the garbage collector update even references created by passing reference parameters?

Yep. It would be rather fragile if it didn't.

Do I have to worry about this at all?

Nope. You're thinking about this like an unmanaged C++ programmer -- C++ makes you do this work, but C# does not. Remember, the whole point of the managed memory model is to free you from having to think about this stuff.

Of course, if you enjoy worrying about this stuff you can always use the "unsafe" feature to turn these safety systems off, and then you can write heap and stack corrupting bugs to your heart's content.

牛↙奶布丁 2024-09-01 20:27:26

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

换句话说 - 别担心。无论它是否可以压缩,都不会给您带来问题。

No, you don't need to worry about it. Basically the calling method (DoIt) has a "live" reference to the instance of Test, which will prevent it from being garbage collected. I'm not sure whether it can be compacted - but I suspect it can, with the GC able to spot which variable references are part of objects being moved.

In other words - don't worry. Whether it can be compacted or not, it shouldn't cause you a problem.

原谅我要高飞 2024-09-01 20:27:26

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

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

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

It is exactly how you mention it in the last sentence. The GC will move all needed references when it compacts the heap (except for references to unmanaged memory).

Note that using the stack or heap is related to an instance variable being of a value or reference type. Value types (structs and 'simple' types like int, double, etc) are always on the stack, classes are always in the heap (what is in the stack is the reference - the pointer - to the allocated memory for the instance).

Edit: as correctly noted below in the comment, the second paragraph was written much too quickly. If a value type instance is a member of a class, it will not be stored in the stack, it will be in the heap like the rest of the members.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文