我假设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}");
}
}
答案 0 :(得分:3)
答案 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中注意到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固定固定的签名编码只能出现在 描述局部变量的签名(§II.15.4.1.3)。一个 固定局部变量的方法正在执行,VES不会 重定位本地引用的对象。也就是说,如果 CLI的实现使用移动对象的垃圾收集器, 收集器不应移动活动引用的对象 固定局部变量。
[基本原理:如果使用非托管指针来取消引用托管 物体,这些物体应固定。例如,这种情况发生在 当托管对象传递给设计用于操作的方法时 非托管数据。最终理由]
VES =虚拟执行系统CLI =公共语言基础结构 CTS =通用类型系统
最后,除了 JITer 和 CLR 之外,钉扎的大部分基础工作都是由 GC
完成的。实际上,GC必须要离开并留下固定的本地 变量单独用于方法的生命周期。通常GC是 关注哪些物体是活的还是死的,以便它知道什么 它必须清理。但是对于固定物体,它必须迈出一步 此外,不仅必须不清理物体,而且绝不能 移动它。通常,GC喜欢重新定位对象 在紧凑阶段期间,使内存分配便宜,但固定 当通过指针访问对象时阻止它 因此它的内存地址必须保持不变。
显然,您主要关注的是碎片问题,您担心 GC 无法清理它。
然而,正如示例所示(并且您可以自己玩它),只要ary
超出范围并且最终确定了固定,GC最终将完全释放它。
注意:我不是一个声誉良好的来源,我找不到官方确认但是我想到了这些我发现的信息片段 可能会引起同样的兴趣