这个不安全的代码也应该在 .NET Core 3 中工作吗?

Should this unsafe code work also in .NET Core 3?

我正在重构我的库以使用 Span<T> 来尽可能避免堆分配,但由于我还针对较旧的框架,因此我也在实施一些通用的后备解决方案。但是现在我发现了一个奇怪的问题,我不太确定我是在 .NET Core 3 中发现了一个错误,还是我在做一些非法的事情。

问题:

// This returns 1 as expected but cannot be used in older frameworks:
private static uint ReinterpretNew()
{
    Span<byte> bytes = stackalloc byte[4];
    bytes[0] = 1; // FillBytes(bytes);

    // returning bytes as uint:
    return Unsafe.As<byte, uint>(ref bytes.GetPinnableReference());
}

// This returns garbage in .NET Core 3.0 with release build:
private static unsafe uint ReinterpretOld()
{
    byte* bytes = stackalloc byte[4];
    bytes[0] = 1; // FillBytes(bytes);

    // returning bytes as uint:
    return *(uint*)bytes;
}

有趣的是,ReinterpretOld 在 .NET Framework 和 .NET Core 2.0 中运行良好(所以我对它很满意),但它还是让我有点困扰。

顺便说一句。 ReinterpretOld 也可以在 .NET Core 3.0 中通过一个小的修改来修复:

//return *(uint*)bytes;
uint* asUint = (uint*)bytes;
return *asUint;

我的问题:

这是一个错误还是 ReinterpretOld 在旧框架中工作只是偶然,我应该为它们应用修复程序吗?

备注:

哦,这是一个有趣的发现;这里发生的是你的本地人正在优化 - 没有本地人剩余,这意味着没有 .locals init,这意味着 stackalloc 的行为 不同 ,并且不擦除 space;

private static unsafe uint Reinterpret1()
{
    byte* bytes = stackalloc byte[4];
    bytes[0] = 1;

    return *(uint*)bytes;
}

private static unsafe uint Reinterpret2()
{
    byte* bytes = stackalloc byte[4];
    bytes[0] = 1;

    uint* asUint = (uint*)bytes;
    return *asUint;
}

变为:

.method private hidebysig static uint32 Reinterpret1() cil managed
{
    .maxstack 8
    L_0000: ldc.i4.4 
    L_0001: conv.u 
    L_0002: localloc 
    L_0004: dup 
    L_0005: ldc.i4.1 
    L_0006: stind.i1 
    L_0007: ldind.u4 
    L_0008: ret 
}

.method private hidebysig static uint32 Reinterpret2() cil managed
{
    .maxstack 3
    .locals init (
        [0] uint32* numPtr)
    L_0000: ldc.i4.4 
    L_0001: conv.u 
    L_0002: localloc 
    L_0004: dup 
    L_0005: ldc.i4.1 
    L_0006: stind.i1 
    L_0007: stloc.0 
    L_0008: ldloc.0 
    L_0009: ldind.u4 
    L_000a: ret 
}

认为 我很高兴地说这是一个编译器错误,或者至少:考虑到 previous decisions have been put in place to say "emit the .locals init",这是一个不受欢迎的副作用和行为, 特别是 试图保持 stackalloc 理智 - 但编译器人员是否同意取决于他们。

解决方法是:将 stackalloc space 视为未定义(公平地说,这就是您的本意);如果您希望它为零:手动将其归零。