이 안전하지 않은 코드가 .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에서 잘 작동하지만 결국 행복 할 수 있습니다.

Btw. 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(). 하지만, 다른 비트에보고
마크 Gravell

다른 사람에게 도움이 될 경우를 대비 한 SharpLab . 피하는 두 버전 Span<T>은 다른 IL로 컴파일합니다. 나는 당신이 잘못된 일을하고 있다고 생각 하지 않습니다 : JIT 버그가 의심됩니다.
canton7

당신이보고있는 쓰레기는 무엇입니까? 해킹을 사용하여 locals-init를 비활성화하고 있습니까? 이 핵은 상당한 영향을 미칩니다 stackalloc(즉, 할당 된 공간을
지우지

@ canton7 동일한 IL로 컴파일하면 JIT 버그라고 추론 할 수 없습니다 ... IL이 같은 경우 등 ... 더 오래된 컴파일러를 사용하는 경우 컴파일러 버그처럼 들립니다. György : 이것을 어떻게 컴파일하고 있는지 정확하게 나타낼 수 있습니까? 예를 들어 어떤 SDK? 나는 쓰레기를 재현 할 수 없다
Marc Gravell

1
stackalloc은 항상 0이 아닌 것처럼 보입니다. 실제로 : link
canton7

답변:


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는 init을 방출 '라고하는 장소에 배치되고 , 특히 시도하고 stackalloc제정신을 유지하십시오 -그러나 컴파일러 사람들의 동의 여부는 그들에게 달려 있습니다.

해결 방법은 stackalloc공간을 정의되지 않은 것으로 처리합니다 (공평하게 말하자면 수행하려는 작업입니다). 0이 될 것으로 예상되는 경우 수동으로 0으로 설정하십시오.


2
이것에 대한 공개 티켓 이있는 것 같습니다 . 나는 그것에 새로운 의견을 추가 할 것입니다.
György Kőszeg

허, 내 모든 작업과 첫 번째 작업이 누락 된 것을 보지 못했습니다 locals init. 좋은데
canton7

1
@ canton7 나처럼 당신이있는 거 아무것도, 당신은 자동으로 건너 뛸 경우 .maxstack.locals하지 통지에 특히 쉽게 그것을 만드는, 그것이는 것을 /하지가 :
마크 Gravell

1
The content of the newly allocated memory is undefined.MSDN에 따르면. 사양에서는 메모리도 제로화되어야한다고 말하지 않습니다. 따라서 우연히 또는 비 계약 적 행동의 결과로 오래된 프레임 워크에서만 작동하는 것처럼 보입니다.
Luaan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.