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

Can an instance creation in fixed block lead to invalid pointers or corrupt memory?

假设一个不安全的方法。有一个固定块通过第一个元素固定数组,即 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 调用分配了一个 new 对象 从缓冲区中复制数据,所以:一旦PtrToStructure完成,它来自哪里并不重要。因此,这段代码没有任何问题。但是,如果您试图在现有缓冲区内“分配”对象:这不是此代码的作用。

如果所涉及的类型是没有引用的 struct(即满足 unmanaged 约束的类型),则有一些方法可以获得托管(即不是 unsafe)引用数据 inside 现有数组,同时强制类型(通常在 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;
}

请注意,通过将 Coerce<T> 签名中的 byte[] 更改为 Span<byte>,这也可以用于任意内存(堆栈、非托管堆等)。