1
class A
{
   public int m_a;
}

void fun(ref int a)
{
   ...
}

fun(ref new A().m_a);

在乐趣中,“ref int a”如何在乐趣返回之前防止对象(新 A())被回收?

<example 0>
using System;

class A
{
    public int m_a;
    ~A()
    {
        Console.WriteLine("~A()");
    }
}

class Program
{
    static void fun(ref int a)
    {
        Console.WriteLine("Begin<<<<<<<<<<<<<<");
        a++;
        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.WriteLine("End>>>>>>>>>>>>>>>>>");
    }

    static void Main(string[] args)
    {
        fun(ref new A().m_a);

        Console.WriteLine("Over");
    }
}


output:
Begin<<<<<<<<<<<<<<
~A()
End>>>>>>>>>>>>>>>>>
Over

<example 1>
using System;

class A
{
    public int m_a;
    ~A()
    {
        Console.WriteLine("~A()");
    }
}

class Program
{
    static void fun(ref int a)
    {
        Console.WriteLine("Begin<<<<<<<<<<<<<<");
        a++;
        GC.Collect();
        GC.WaitForPendingFinalizers();

        //add a code
        a++;

        Console.WriteLine("End>>>>>>>>>>>>>>>>>");
    }

    static void Main(string[] args)
    {
        fun(ref new A().m_a);

        Console.WriteLine("Over");
    }
}

output:
Begin<<<<<<<<<<<<<<
End>>>>>>>>>>>>>>>>>
Over
~A()

请在 VS 中按发布模式构建。我查看ASM代码,只加了两行:

             a++;
0000002f  mov         eax,dword ptr [ebp-4] 
00000032  inc         dword ptr [eax] 

两个示例之间的其他部分相同。GC 如何确保变量 a 在机器代码中不再有用?

4

3 回答 3

2

这取决于如何afun. GC 能够确定任何给定的对象是否是根对象。在这种情况下,是一个实例中a字段的别名,并且该对象被认为是有根的。但是,如果 JIT 编译器确定从该点开始在该方法的其余部分中未使用,则该实例将不再是根并且有资格进行垃圾收集。m_aAafunA

一些例子:

void fun(ref int a)
{
   // forgot to use a. our object is already eligible for GC!
   for(int i = 0; i < 10; i++)
   {
      Console.WriteLine(i);
   }
}

void fun2(ref int a)
{       
   for(int i = 0; i < 10; i++)
   {
      Console.WriteLine(a);
   }
   // GC has to wait until a is no longer in use. now our object is eligible for GC.
}

void fun3(ref int a)
{
   // can't gc yet. a is still being used.
   int b = a;
   // b has a copy of the value in a so now our object is eligible for GC.
   for(int i = 0; i < 10; i++)
   {
      Console.WriteLine(b);
   }
}

更新:

我不是如何在 clr 中实现这一点的专家,但我的理解是使用 ref 会导致将指向该字段的托管指针m_a传递到fun. 当 GC 运行时,根由对静态堆对象的引用、所有线程的调用堆栈和寄存器确定。我在这里猜测,但也许指向该字段的托管指针m_a存储了对容器对象的引用。或者,GC 可以确定给定托管指针在哪个对象中。无论哪种方式,对象都被标记为源自该托管引用。

于 2012-07-03T07:33:05.467 回答
1

You would have to create the A instance beforehand and keep a reference to it, along the lines of:

    A a = new A();
    fun(ref a.m_a);

Otherwise, when fun returns, the new instance of A goes out of scope, and is therefore up for garbage collection.

于 2012-07-03T07:27:24.973 回答
1

I'd thought this old presentation (Slide 30 onwards) to be sufficient, but it developed into a bit of back-and-forth in the comments section, so I thought I'd put an answer up.

Whenever the JIT prepares any method, it also constructs a "table", that maps which local variable slots are "live" at any particular point in the method. So, when the GC is examining each thread, it takes the Instruction Pointer for that thread, consults the table, and uses that to determine live references within the current method.

There is nothing written into the machine code for a particular method that has to notify the GC of anything - the JITs analysis covers all paths through the code, and only has to be done once for each method.

Under Debug, the JIT marks all variables as used for the entire body of the method - this keeps references alive longer than strictly necessary, but does mean that you can examine the state of variables after their last use in the method (via e.g. Locals or Autos windows, or any other way you may suddenly wish to reference a variable)

于 2012-07-03T13:09:23.153 回答