web-dev-qa-db-ja.com

この安全でないコードは.NET Core 3でも機能しますか?

ヒープ割り当てを回避するために_Span<T>_を使用するようにライブラリをリファクタリングしていますが、古いフレームワークも対象としているため、いくつかの一般的なフォールバックソリューションも実装しています。しかし今、私は奇妙な問題を発見しました。NETCore 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に適用しようとしましたが、効果がありませんでした。
42
György Kőszeg

おお、これは楽しい発見です。ここで何が起こっているかは、ローカルが最適化されていることです-ローカルが残っていない、つまり.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 sane-コンパイラを人々は同意することは彼ら次第です。

回避策は次のとおりです。stackallocスペースを未定義として扱います(公平にするために、これはあなたが意図していることです)。ゼロになることが予想される場合:手動でゼロにします。

35
Marc Gravell