问题描述
假设一个不安全的方法。有一个固定块通过第一个元素固定数组,即 fixed (void* p = &array[0])
。在该块中,实例由 new
关键字或 Marshal.PtrToStructure
创建。我是否冒着 p
指向无效地址的风险?这会破坏内存吗?
示例:
unsafe object ArrayToObject(Byte[] array,Type type)
{
// Precondition checks on arguments
fixed (void* p = &array[0])
{
return Marshal.PtrToStructure(new IntPtr(p),type));
}
}
我在上述实现中遇到了一个相当罕见的问题,即生成的对象与数组中的数据不匹配。 (最后一段要么全为 0,要么很明显它不可能是那个。)这个方法有时也被频繁调用,通常相隔不到 1 毫秒。数组分配也以这样的频率发生在单独的线程上,因此这也可能导致我的问题。 我也没有以这种强度使用该软件,因此我无法真正重现该问题。此外,我目前只在 .NET 4.0 上看到过这个问题。
我最近读了一本书,建议避免在固定块中进行堆分配,但除此之外还没有看到任何提及风险的内容,同时看到了大量保证对象已固定,并且将不被移动。我还没有完成这本书,所以不确定这是否解释了这一点。 (至少不会立即解释这种事情。)
最近还阅读了 GC 如何在第一代、第二代和第三代堆之间移动小对象,不幸的是 array
足够小(不超过 1,000 个元素),因此如果不是固定块。
目前我已切换到 GCHandle
,我希望它可以解决此问题,但这让我很烦恼。
由于向后兼容,我对 .NET 4.0 和 .NET 4.7 的这种行为很感兴趣。
解决方法
在 fixed
块内,p
被完美定义,PtrToStructure
调用分配了一个新对象复制数据 来自缓冲区,所以:一旦 PtrToStructure
完成,它来自哪里并不重要。因此,这段代码没有任何问题。但是,如果您尝试在现有缓冲区内“分配”对象:这不是这段代码的作用。
如果所涉及的类型是没有引用的 struct
(即满足 unmanaged
约束的类型),则有多种方法可以获得对的托管(即不是 unsafe
)引用现有数组里面的数据,同时强制类型(通常在byte
和一些自定义的struct
之间),例如:
static ref T Coerce<T>(byte[] array) where T : unmanaged
=> ref MemoryMarshal.Cast<byte,T>(array)[0];
使用:
static void Main()
{
byte[] data = new byte[16];
Console.WriteLine(BitConverter.ToString(data));
ref SomeStruct val = ref Coerce<SomeStruct>(data);
val.a = 1;
val.b = 2;
val.c = 3;
Console.WriteLine(BitConverter.ToString(data));
}
struct SomeStruct
{ // note: structs should usually be "readonly"; this
// is for convenience of example only
public int a,b,c;
}
请注意,通过将 byte[]
签名中的 Span<byte>
更改为 Coerce<T>
,这也适用于任何任意内存(堆栈、非托管堆等)。