这种不安全的代码是否也可以在.NET Core 3中使用?


42

我正在重构我的库,以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但没有效果。

2
仅供参考:return Unsafe.As<byte, uint>(ref bytes[0]);return MemoryMarshal.Cast<byte, uint>(bytes)[0];-无需使用GetPinnableReference();不过,他正在寻找另一部分
Marc Gravell

SharpLab可以帮助其他人。避免使用的两个版本可以Span<T>编译为不同的IL。我认为您没有做任何无效的事情:我怀疑是JIT错误。
canton7 '19

您看到的垃圾是什么?您是否在使用hack禁用locals-init?此hack会产生重大影响stackalloc(即不会擦除分配的空间)
Marc Gravell

@ canton7如果它们编译为相同的IL,我们不能推断出它是JIT错误...如果IL相同,等等...听起来更像是编译器错误,如果有的话,也许是使用较旧的编译器?György:您能确切说明如何编译吗?例如什么SDK?我无法复制垃圾
Marc Gravell

1
看起来stackalloc并不总是为零,实际上:link
canton7 '19

Answers:


35

哦,这是一个有趣的发现;这里发生的事情是您的本地人正在被优化—没有剩余的本地人,这意味着没有本地人.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初始化”特别是尝试和保持stackalloc理智-但是编译器人员是否同意取决于他们。

解决方法是:将stackalloc空间视为未定义的空间(公平地说,这就是您要执行的操作);如果您希望它为零:请手动将其归零。


2
似乎有一张公开票。我将对此添加新的评论。
捷尔吉科塞格

呵呵,我所有的工作,我都没注意到前一个工作不见了locals init。好一个。
canton7 '19

1
@ canton7如果您像我一样,会自动跳过.maxstack.locals,从而特别容易不注意到它是否存在:)
Marc Gravell

1
The content of the newly allocated memory is undefined.根据MSDN。规范也没有说存储器也应该归零。因此,看起来它只能偶然或由于非合同行为而在旧框架上运行。
罗安
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.