C # ส่งคืนตัวชี้ที่สร้างด้วย stackalloc ภายในฟังก์ชัน

ฉันมีรหัส C# ที่โต้ตอบกับรหัส C++ ซึ่งดำเนินการกับสตริง

ฉันมีโค้ดชิ้นนี้ในคลาสตัวช่วยแบบคงที่:

internal static unsafe byte* GetConstNullTerminated(string text, Encoding encoding)
{
    int charCount = text.Length;
    fixed (char* chars = text)
    {
        int byteCount = encoding.GetByteCount(chars, charCount);
        byte* bytes = stackalloc byte[byteCount + 1];
        encoding.GetBytes(chars, charCount, bytes, byteCount);
        *(bytes + byteCount) = 0;
        return bytes;
    }
}

อย่างที่คุณเห็น มันจะส่งคืนตัวชี้ไปยังไบต์ที่สร้างด้วยคีย์เวิร์ด stackalloc
อย่างไรก็ตาม จากข้อกำหนด C# 18.8:

บล็อกหน่วยความจำที่จัดสรรสแต็กทั้งหมดที่สร้างขึ้นระหว่างการดำเนินการของสมาชิกฟังก์ชันจะถูกยกเลิกโดยอัตโนมัติเมื่อสมาชิกฟังก์ชันนั้นกลับมา

หมายความว่าตัวชี้ไม่ถูกต้องจริง ๆ ทันทีที่เมธอดส่งคืนใช่หรือไม่

การใช้วิธีนี้ในปัจจุบัน:

byte* bytes = StringHelper.GetConstNullTerminated(value ?? string.Empty, Encoding);
DirectFunction(NativeMethods.SCI_SETTEXT, UIntPtr.Zero, (IntPtr) bytes);

ควรเปลี่ยนรหัสเป็น

...
int byteCount = encoding.GetByteCount(chars, charCount);
byte[] byteArray = new byte[byteCount + 1];
fixed (byte* bytes = byteArray)
{
    encoding.GetBytes(chars, charCount, bytes, byteCount);
    *(bytes + byteCount) = 0;
}
return byteArray;

และใช้ fixed อีกครั้งในอาร์เรย์ที่ส่งคืนเพื่อส่งพอยน์เตอร์ไปยังเมธอด DirectFunction หรือไม่

ฉันกำลังพยายามลดจำนวนการใช้งาน fixed ให้เหลือน้อยที่สุด (รวมถึงคำสั่ง fixed ในการโอเวอร์โหลดอื่น ๆ ของ GetByteCount() และ GetBytes() จาก Encoding)

tl;dr

  1. ตัวชี้ไม่ถูกต้องทันทีที่เมธอดส่งคืนหรือไม่ ไม่ถูกต้อง ณ จุดที่ส่งต่อไปยัง DirectFunction() หรือไม่

  2. หากเป็นเช่นนั้น วิธีที่ดีที่สุดในการใช้คำสั่ง fixed น้อยที่สุดเพื่อให้บรรลุภารกิจคืออะไร


person A. A. Ron    schedule 06.05.2017    source แหล่งที่มา


คำตอบ (2)


หมายความว่าตัวชี้ไม่ถูกต้องจริง ๆ ทันทีที่เมธอดส่งคืนใช่หรือไม่

ใช่ มันไม่ถูกต้องทางเทคนิค - แม้ว่าแทบจะตรวจไม่พบก็ตาม สถานการณ์นี้เกิดขึ้นกับตัวเองผ่านทาง unsafe การดำเนินการใดๆ ในหน่วยความจำนั้นจะมีพฤติกรรมที่ไม่ได้กำหนดไว้ในขณะนี้ ทุกสิ่งที่คุณทำ แต่โดยเฉพาะอย่างยิ่งวิธีการเรียก อาจสุ่มเขียนทับหน่วยความจำนั้น - หรือไม่ ขึ้นอยู่กับขนาดและความลึกของสแต็กเฟรมสัมพัทธ์

สถานการณ์นี้โดยเฉพาะเป็นหนึ่งในสถานการณ์ที่การเปลี่ยนแปลง ref ที่เสนอในอนาคตหวังว่าจะกำหนดเป้าหมาย ซึ่งหมายถึง: การอนุญาตให้ stackalloc เป็น ref (แทนที่จะเป็นตัวชี้) โดยคอมไพเลอร์รู้ว่ามันเป็นประเภทที่อ้างอิงถึงสแต็ก ref หรือประเภทการอ้างอิง และ ดังนั้นจึงไม่อนุญาตให้ ref-ส่งคืนค่านั้น

ท้ายที่สุด เมื่อคุณพิมพ์ unsafe คุณกำลังพูดว่า "ฉันรับผิดชอบอย่างเต็มที่หากสิ่งนี้ผิดพลาด" ในกรณีนี้มันผิดจริงๆ


ถูกต้อง ที่จะใช้ตัวชี้ ก่อน ออกจากวิธีการ ดังนั้นแนวทางหนึ่งที่ใช้งานได้อาจเป็น (สมมติว่าคุณต้องการ API วัตถุประสงค์ทั่วไปที่ค่อนข้างเป็นธรรม) เพื่อให้ผู้เรียกส่งผ่านใน มอบหมายหรืออินเทอร์เฟซที่ระบุสิ่งที่ผู้โทรต้องการให้คุณ ทำ ด้วยตัวชี้ เช่น

StringHelper.GetConstNullTerminated(value ?? string.Empty, Encoding,
    ptr => DirectFunction(NativeMethods.SCI_SETTEXT, UIntPtr.Zero, (IntPtr) ptr));

กับ:

unsafe delegate void PointerAction(byte* ptr);
internal static unsafe void GetConstNullTerminated(string text, Encoding encoding,
    PointerAction action)
{
    int charCount = text.Length;
    fixed (char* chars = text)
    {
        int byteCount = encoding.GetByteCount(chars, charCount);
        byte* bytes = stackalloc byte[byteCount + 1];
        encoding.GetBytes(chars, charCount, bytes, byteCount);
        *(bytes + byteCount) = 0;
        action(bytes);
    }
}

โปรดทราบว่าสตริงที่มีขนาดใหญ่มากอาจทำให้คุณล้นกองซ้อน

person Marc Gravell    schedule 06.05.2017
comment
ไม่เคยคิดจะผ่านตัวแทน โซลูชันนี้จะเร็วเท่ากับการคัดลอกและวางในบรรทัด GetConstNullTerminated ลงในปลายทางด้วยตนเอง หากไม่มีข้อเสียด้านประสิทธิภาพในการใช้ผู้รับมอบสิทธิ์ ฉันจะต้องทำการวิจัยเกี่ยวกับการแสดงของผู้แทนตอนนี้... - person A. A. Ron; 06.05.2017
comment
ประสิทธิภาพการมอบหมายของ @A.A.Ron จะไม่สามารถวัดผลได้อย่างสมบูรณ์เมื่อเปรียบเทียบกับงานการเข้ารหัสและการเดินสองครั้งของสตริงอินพุต (หนึ่งครั้งสำหรับความยาว ครั้งหนึ่งสำหรับการเข้ารหัส) - person Marc Gravell; 06.05.2017
comment
ผู้รับมอบสิทธิ์จะปรับปรุงประสิทธิภาพมากกว่าการจัดสรรฮีปจริงหรือไม่ ผู้รับมอบสิทธิ์ยังคงต้องการการจัดสรรฮีป ขึ้นอยู่กับขนาดของสตริงที่ส่งผ่าน คุณสามารถจัดสรรหน่วยความจำเพิ่มเติมเพื่อเก็บผู้รับมอบสิทธิ์ได้มากกว่าสตริงจริงได้อย่างง่ายดาย - person Jarra McIntyre; 07.05.2017
comment
@Jarra แลมบ์ดาที่แสดงไม่ได้จับตัวแปรใด ๆ และด้วยเหตุนี้จริง ๆ แล้วได้รับการสนับสนุนโดยฟิลด์คงที่ที่เก็บอินสแตนซ์ผู้รับมอบสิทธิ์ที่นำมาใช้ซ้ำ ดังนั้นจึงไม่มีค่าใช้จ่ายการจัดสรรอย่างต่อเนื่องหลังจากการโทรครั้งแรก ในกรณีทั่วไป อินสแตนซ์ผู้รับมอบสิทธิ์มีขนาดไม่ใหญ่ - ตามแนวคิดเป็นเพียงอินสแตนซ์เป้าหมายและตัวชี้วิธีการ - โดยพื้นฐานแล้ว 16 ไบต์บน x64 (บวกส่วนหัวของวัตถุบวกค่าโสหุ้ยอื่น ๆ ) - ไม่แพง แต่แน่นอนว่าเป็นศูนย์การจัดสรร (เช่น แสดง) ราคาถูกกว่า - person Marc Gravell; 07.05.2017

stackalloc ทำให้เกิดการจัดสรรหน่วยความจำบนสแต็ก สแต็กจะคลายออกโดยอัตโนมัติเมื่อฟังก์ชันกลับมา C# กำลังปกป้องคุณจากการสร้างตัวชี้ที่ค้างโดยไม่อนุญาตให้คุณส่งคืนตัวชี้ เนื่องจากไม่มีทางเป็นไปได้ที่หน่วยความจำจะยังคงใช้งานได้หลังจากที่สแต็กถูกคลายออกเมื่อฟังก์ชันกลับมา

หากคุณต้องการให้หน่วยความจำอยู่นอกเหนือขอบเขตของฟังก์ชันที่จัดสรร คุณจะไม่สามารถจัดสรรหน่วยความจำบนสแต็กได้ คุณต้องจัดสรรบนฮีปผ่านทางใหม่

person Jarra McIntyre    schedule 06.05.2017
comment
C# กำลังปกป้องคุณจากการสร้างตัวชี้ที่ค้างโดยไม่ให้คุณส่งคืนตัวชี้ C# ไม่ได้บ่นเลย และโค้ดก็คอมไพล์ได้โดยไม่มีปัญหา - person A. A. Ron; 06.05.2017
comment
ขอโทษด้วย. ฉันคิดว่า C# มีการวิเคราะห์การหลีกหนีขั้นพื้นฐานเพื่อตรวจจับกรณีเช่นนี้ - person Jarra McIntyre; 07.05.2017