作为FCin所评论的,
指针不能比其所指向的资源存在更长的生命周期,因为C#保护它不会出现悬挂指针的情况。因此,如果您指向一个局部变量,那么一旦该变量超出作用域,它就一定会被释放。在这种情况下,一旦FooUnsafe返回,指针也会被释放。
还要注意JuanR的评论,
fixed语句(C#参考)执行完语句中的代码后,任何固定的变量都将解除固定并可能面临垃圾回收。
然而,让我们通过一个简单的例子和一些互联网信息片段来证明它。
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
有趣的是,在某些情况下编译器会将finally优化掉,因此您不一定总能看到它。
Roslyn源代码中的LocalRewriter_FixedStatement.cs
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
IL_0020: ret
}
一些背景信息
标准 ECMA-335 Common Language Infrastructure (CLI)
II.7.1.2 pinned 仅当签名描述局部变量(§II.15.4.1.3)时,才应在固定的签名编码中出现。当具有固定局部变量的方法正在执行时,VES不会将局部变量引用的对象重新定位。也就是说,如果CLI的实现使用移动对象的垃圾回收器,则垃圾回收器不会移动由活动固定局部变量引用的对象。
【原理:如果使用非托管指针对托管对象进行解引用,则这些对象应该被固定。例如,当将托管对象传递给设计用于操作非托管数据的方法时,就会发生这种情况。结束原理】
VES = Virtual Execution System CLI = Common Language Infrastructure CTS = Common Type System
最后,除了 JITer 和 CLR,大多数固定的工作都是由 GC 完成的
实际上,GC必须离开并在整个方法的生命周期内保留 固定的局部变量。通常,GC关心哪些对象是活动的或死亡的,以便知道它必须清理什么。但对于固定的对象,它必须进一步进行,不仅不能清理对象,还不能移动它。通常,在紧凑阶段期间,GC喜欢在对象周围重新定位对象,以使内存分配变得便宜,但固定会阻止这种情况发生,因为对象通过指针访问,因此其内存地址必须保持不变。
显然,您最关心的问题是碎片问题,并且您担心 GC 无法清理它。
![enter image description here](https://istack.dev59.com/jXauL.webp)
然而,正如示例所示(您可以自己尝试),一旦 ary
超出范围并且 fixed 完成,GC 最终将完全释放它。
注意:我不是可靠的来源,并且找不到 官方确认,但我认为我找到的这些信息片段可能仍然很有趣
FooUnsafe
返回,指针也将失效。 - FCin