如果抛出异常,"fixed" 是否得到正确清理?
Does "fixed" get cleaned up properly if an Exception is thrown?
我假设 fixed
的实现类似于 using
/try..finally
,因为如果块提前终止(通过 return 或抛出异常),指针得到正确清理("unfixed" 以便 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}");
}
}
它基于范围。
在 fixed
块中,句柄 table 中的对象将是 "pinned" 并且 GC 不会意外地重定位变量。
当抛出异常时,您将超出固定范围,GC 将不会考虑固定的内存位置。
我不知道内部实现,但 GC 可能会检查某个线程的执行点,并基于此确定是否允许重新定位(即基于是否在固定块内) .
您不需要将其放在 try/catch/finally
块中。
来自文档:
After the code in the statement is executed, any pinned variables are
unpinned and subject to garbage collection.
据 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.
但是,让我们尝试用一个简单的例子和互联网上的一些信息片段来证明这一点
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,因为编译器会在某些情况下优化它
LocalRewriter_FixedStatement.cs 在 Roslyn 源中
// 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 无法清理它。
然而,正如示例所示(您可以自己玩),一旦 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
我假设 fixed
的实现类似于 using
/try..finally
,因为如果块提前终止(通过 return 或抛出异常),指针得到正确清理("unfixed" 以便 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}");
}
}
它基于范围。
在 fixed
块中,句柄 table 中的对象将是 "pinned" 并且 GC 不会意外地重定位变量。
当抛出异常时,您将超出固定范围,GC 将不会考虑固定的内存位置。
我不知道内部实现,但 GC 可能会检查某个线程的执行点,并基于此确定是否允许重新定位(即基于是否在固定块内) .
您不需要将其放在 try/catch/finally
块中。
来自文档:
After the code in the statement is executed, any pinned variables are unpinned and subject to garbage collection.
据 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.
但是,让我们尝试用一个简单的例子和互联网上的一些信息片段来证明这一点
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,因为编译器会在某些情况下优化它
LocalRewriter_FixedStatement.cs 在 Roslyn 源中
// 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 无法清理它。
然而,正如示例所示(您可以自己玩),一旦 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