在固定块中创建实例会导致指针无效或内存损坏吗?

问题描述

假设一个不安全的方法。有一个固定块通过第一个元素固定数组,即 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>,这也适用于任何任意内存(堆栈、非托管堆等)。