c# – 如果抛出异常,“固定”是否可以正确清理?

我假设fixed的实现类似于using / try..finally,因为如果块提前终止(通过返回或抛出异常),指针会被正确清理(“不固定”以便GC可以完成其工作再次).

但是,我没有在fixed documentation中看到这样的保证,所以我想知道某处是否存在某种形式的官方保证,或者我是否应该在每个固定区块中引入try..catch.

unsafe void FooUnsafe()
{
    var str = "Foo";
    try
    {
        fixed (char* pStr = str)
        {
            Console.WriteLine("First Char: " + (*pStr));
            throw new Exception("Test");
        }
    }
    catch (Exception ex) {
        Console.WriteLine($"Exception when working with {str}: {ex.Message}");      
    }
}

解决方法:

正如FCin所评论的那样

Pointer cannot live longer than the resource that it points to because
C# protects it from happening because of Dangling Pointer. So if you
point to a local variable, then it will definitely be disposed once
the variable goes out of scope. In this situation, once the FooUnsafe
returns.

JuanR也注意到了

fixed Statement (C# Reference)

After the code in the statement is executed, any pinned variables are
unpinned and subject to garbage collection.

但是,让我们尝试用一个简单的例子来证明它,以及围绕Interwebs的一些信息片段

private static unsafe void Main()
{
   Console.WriteLine($"Total Memory: {GC.GetTotalMemory(false)}");

   var arr = new int[100000];

   Console.WriteLine($"Total Memory after new : {GC.GetTotalMemory(false)}");

   try
   {

      fixed (int* p = arr)
      {
         *p = 1;
         throw new Exception("rah");
      }

   }
   catch 
   {
   }

   Console.WriteLine($"Generation: {GC.GetGeneration(arr)}, Total Memory: {GC.GetTotalMemory(false)}");

   arr = null;
   GC.Collect();
   GC.WaitForPendingFinalizers();
   Console.WriteLine("Total Memory: {0}", GC.GetTotalMemory(false));
   Console.Read();
}

结果

Total Memory: 29948
Total Memory after new: 438172
Generation: 2, Total Memory: 438172
Total Memory: 29824

你会在IL中注意到finally和ldnull

.try
{

   // [23 14 - 23 26]
   IL_0043: ldloc.0      // arr
   IL_0044: dup          
   IL_0045: stloc.2      // V_2
   IL_0046: brfalse.s    IL_004d
   IL_0048: ldloc.2      // V_2
   IL_0049: ldlen        
   IL_004a: conv.i4      
   IL_004b: brtrue.s     IL_0052
   IL_004d: ldc.i4.0     
   IL_004e: conv.u       
   IL_004f: stloc.1      // p
   IL_0050: br.s         IL_005b
   IL_0052: ldloc.2      // V_2
   IL_0053: ldc.i4.0     
   IL_0054: ldelema      [mscorlib]System.Int32
   IL_0059: conv.u       
   IL_005a: stloc.1      // p

   ...

} // end of .try
finally
{

   IL_006a: ldnull       
   IL_006b: stloc.2      // V_2
   IL_006c: endfinally   
} // end of finally

一个有趣的注意事项是,你不会总是看到最终,因为编译器会在某些情况下优化它

LocalRewriter_FixedStatement.cs在罗斯林来源

// In principle, the cleanup code (i.e. nulling out the pinned variables) is always
// in a finally block.  However, we can optimize finally away (keeping the cleanup
// code) in cases where both of the following are true:
//   1) there are no branches out of the fixed statement; and
//   2) the fixed statement is not in a try block (syntactic or synthesized).
if (IsInTryBlock(node) || HasGotoOut(rewrittenBody))
{

即使它生活在这样的方法中

private static unsafe void test(int[] arr)
{
   fixed (int* p = arr)
   {
      *p = 1;
   }
}

你会注意到的

.method private hidebysig static void 
   test(
   int32[] arr
   ) cil managed 
{
   .maxstack 2
   .locals init (
   [0] int32* p,
   [1] int32[] pinned V_1
   )

   ...

   IL_001e: ldnull       
   IL_001f: stloc.1      // V_1

   // [54 7 - 54 8]
   IL_0020: ret          

} // end of method MyGCCollectClass::test

一些背景

Standard ECMA-335 Common Language Infrastructure (CLI)

II.7.1.2 pinned The signature encoding for pinned shall appear only in
signatures that describe local variables (§II.15.4.1.3). While a
method with a pinned local variable is executing, the VES shall not
relocate the object to which the local refers. That is, if the
implementation of the CLI uses a garbage collector that moves objects,
the collector shall not move objects that are referenced by an active
pinned local variable.

[Rationale: If unmanaged pointers are used to dereference managed
objects, these objects shall be pinned. This happens, for example,
when a managed object is passed to a method designed to operate with
unmanaged data. end rationale]

VES = Virtual Execution System CLI = Common Language Infrastructure
CTS = Common Type System

最后,除了JITer和CLR之外,钉扎的大部分基础工作都是由GC完成的

In effect the GC has to get out of the way and leave the pinned local
variable alone for the life-time of the method. Normally the GC is
concerned about which objects are live or dead so that it knows what
it has to clean up. But with pinned objects it has to go one step
further, not only must it not clean up the object, but it must not
move it around. Generally the GC likes to relocate objects around
during the Compact Phase to make memory allocations cheap, but pinning
prevents that as the object is being accessed via a pointer and
therefore its memory address has to remain the same.

显然你的主要关注点是碎片问题,你担心GC无法清理它.

c# – 如果抛出异常,“固定”是否可以正确清理?

然而,正如示例所示(并且您可以自己玩它),一旦ary超出范围并且最终确定了固定,GC将最终完全释放它.

Note : I am not a reputable source, and i could find no Official Confirmation however i thought these snippets of information i found
might be of interest all the same

上一篇:c# – Marshal.SizeOf和sizeof之间的区别,我只是不明白


下一篇:并发编程之Unsafe魔术类的魔术