รหัสที่ไม่ปลอดภัยนี้ควรทำงานใน. 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
FYI: return Unsafe.As<byte, uint>(ref bytes[0]);หรือreturn MemoryMarshal.Cast<byte, uint>(bytes)[0];- ไม่จำเป็นต้องใช้GetPinnableReference(); มองไปที่อีกนิด
Marc Gravell

SharpLabในกรณีที่ช่วยคนอื่น สองเวอร์ชันที่หลีกเลี่ยงการSpan<T>คอมไพล์ไปยัง IL ที่แตกต่างกัน ฉันไม่คิดว่าคุณกำลังทำสิ่งใดที่ไม่ถูกต้อง: ฉันสงสัยว่าข้อบกพร่องของ JIT
canton7

ขยะที่คุณเห็นคืออะไร คุณใช้แฮ็คเพื่อปิดการใช้งาน local-init หรือไม่ การแฮ็คนี้ส่งผลกระทบอย่างมีนัยสำคัญstackalloc (เช่นไม่ให้ลบพื้นที่ที่จัดสรรไว้)
Marc Gravell

@ canton7 หากพวกเขารวบรวม IL เดียวกันเราไม่สามารถอนุมานได้ว่าเป็นข้อบกพร่องของ JIT ... ถ้า IL นั้นเหมือนกัน ฯลฯ ... ฟังดูคล้ายกับข้อผิดพลาดของคอมไพเลอร์ถ้ามีอะไรเป็นไปได้ไหมที่คอมไพเลอร์รุ่นเก่า? György: คุณสามารถระบุได้อย่างชัดเจนว่าคุณรวบรวมมันได้อย่างไร? SDK อะไรเช่น ฉันไม่สามารถทำซ้ำขยะ
Marc Gravell

1
ดูเหมือน stackalloc จะไม่เป็นศูนย์จริง ๆ แล้ว: 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พื้นที่เป็นไม่ได้กำหนด (ซึ่งเป็นธรรมคือสิ่งที่คุณตั้งใจจะทำ); หากคุณคาดว่าจะเป็นศูนย์: ศูนย์ด้วยตนเอง


2
ดูเหมือนว่าจะมีตั๋วเปิดสำหรับเรื่องนี้ ฉันจะเพิ่มความคิดเห็นใหม่ไปที่
GyörgyKőszeg

locals initอืมมมทำงานทั้งหมดของฉันและฉันไม่ได้สังเกตคนแรกที่ขาดหายไป ทำได้ดีนี่.
canton7

1
@ canton7 ถ้าคุณอะไรที่คุณชอบฉันคุณจะข้ามผ่าน.maxstackและ.localsทำให้มันโดยเฉพาะอย่างยิ่งเรื่องง่ายที่จะไม่แจ้งให้ทราบว่ามันเป็น / ไม่ได้มี :)
Marc Gravell

1
The content of the newly allocated memory is undefined.ตาม MSDN สเปคไม่ได้บอกว่าหน่วยความจำควรเป็นศูนย์เช่นกัน ดังนั้นดูเหมือนว่าจะทำงานเฉพาะในกรอบเก่าโดยบังเอิญหรือเป็นผลมาจากพฤติกรรมที่ไม่ใช่สัญญา
Luaan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.