43

如果可能,我正在重构我的库以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只是偶然在较旧的框架中起作用,我应该也为它们应用修复程序吗?

评论:

  • 调试版本也适用于 .NET Core 3.0
  • 我试图申请[MethodImpl(MethodImplOptions.NoInlining)]ReinterpretOld但没有效果。
4

1 回答 1

36

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

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 
}

我很高兴地说这是一个编译器错误,或者至少:一个不受欢迎的副作用和行为,因为之前的决定已经到位,说“发出 .locals init”特别是尝试和保持stackalloc理智 - 但编译器人员是否同意取决于他们。

解决方法是:将stackalloc空间视为未定义(公平地说,这是您应该做的);如果您希望它为零:手动将其归零。

于 2019-11-26T13:45:11.757 回答