是"固定"如果抛出异常,请正确清理?

时间:2018-03-13 19:00:57

标签: c# .net unsafe

我假设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}");      
    }
}

3 个答案:

答案 0 :(得分:3)

来自文档:

  

执行语句中的代码后,任何固定的变量都是   取消固定并进行垃圾收集。

fixed Statement (C# Reference)

答案 1 :(得分:2)

它基于范围。

fixed块中,在句柄表中,对象将被固定"并且GC不会不可预测地重新定位变量。

当抛出异常时,您将不在固定范围内,并且GC不会认为该内存位置固定。

我不知道内部实现,但是GC可能会检查某个线程的执行点并根据它确定是否允许重新定位(即基于是否在固定块内)

您不需要在try/catch/finally区块中拥有它。

答案 2 :(得分:2)

由FCin评论

  

指针的寿命不能超过它指向的资源,因为   由于悬空指针,C#可以防止它发生。所以,如果你   指向一个局部变量,它肯定会被处置一次   变量超出范围。在这种情况下,一旦FooUnsafe   回报。

JuanR也注意到

fixed Statement (C# Reference)

  

执行语句中的代码后,任何固定的变量都是   取消固定并进行垃圾收集。

但是,让我们尝试用一个简单的例子来证明它,以及围绕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中注意到finallyldnull

.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固定固定的签名编码只能出现在   描述局部变量的签名(§II.15.4.1.3)。一个   固定局部变量的方法正在执行,VES不会   重定位本地引用的对象。也就是说,如果   CLI的实现使用移动对象的垃圾收集器,   收集器不应移动活动引用的对象   固定局部变量。

     

[基本原理:如果使用非托管指针来取消引用托管   物体,这些物体应固定。例如,这种情况发生在   当托管对象传递给设计用于操作的方法时   非托管数据。最终理由]

     

VES =虚拟执行系统CLI =公共语言基础结构   CTS =通用类型系统

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

完成的。
  

实际上,GC必须要离开并留下固定的本地   变量单独用于方法的生命周期。通常GC是   关注哪些物体是活的还是死的,以便它知道什么   它必须清理。但是对于固定物体,它必须迈出一步   此外,不仅必须不清理物体,而且绝不能   移动它。通常,GC喜欢重新定位对象   在紧凑阶段期间,使内存分配便宜,但固定   当通过指针访问对象时阻止它   因此它的内存地址必须保持不变。

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

enter image description here

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

  

注意:我是一个声誉良好的来源,我找不到官方确认但是我想到了这些我发现的信息片段   可能会引起同样的兴趣