0Pricing
C# Academy · บทเรียน

โค้ดไม่ปลอดภัย พอยน์เตอร์ และบัฟเฟอร์ขนาดคงที่

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

โค้ดไม่ปลอดภัย พอยน์เตอร์ และบัฟเฟอร์ขนาดคงที่ เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C# Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

โค้ดที่ไม่ปลอดภัยคืออะไร

คีย์เวิร์ด unsafe ของ C# เปิดให้จัดการหน่วยความจำโดยตรง ทั้งพอยน์เตอร์ดิบ การคำนวณทางพอยน์เตอร์ และบัฟเฟอร์ขนาดคงที่ การทำเช่นนี้จะข้ามการรับประกันด้านความปลอดภัยของ GC ให้เปิดใช้งานด้วย <AllowUnsafeBlocks>true</AllowUnsafeBlocks> ในไฟล์โครงการ

// Must add to .csproj:
// <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

// Unsafe blocks can appear inside methods:
unsafe
{
    int x = 42;
    int* ptr = &x;          // get address of x
    Console.WriteLine(*ptr); // dereference — prints 42
    *ptr = 100;
    Console.WriteLine(x);    // 100 — x was mutated via pointer
}

ชนิดพอยน์เตอร์ใน C#

ไวยากรณ์พอยน์เตอร์คล้ายกับ C/C++: T* คือพอยน์เตอร์ที่ชี้ไปยัง T คุณสามารถประกาศพอยน์เตอร์ไปยังชนิดค่าแบบไม่มีการจัดการ เช่น int, double และโครงสร้างที่ไม่มีฟิลด์อ้างอิงได้ แต่ไม่อนุญาตให้ใช้พอยน์เตอร์กับชนิดข้อมูลที่จัดการโดย .NET

unsafe
{
    int    i = 10;
    double d = 3.14;

    int*    ip = &i;
    double* dp = &d;

    // Dereference with *
    Console.WriteLine(*ip); // 10

    // Pointer arithmetic — move to next int in memory
    int[] arr = { 1, 2, 3 };
    fixed (int* p = arr)
    {
        Console.WriteLine(*(p + 0)); // 1
        Console.WriteLine(*(p + 1)); // 2
        Console.WriteLine(*(p + 2)); // 3
    }
}

คำสั่ง fixed

อ็อบเจ็กต์ที่จัดการโดย .NET อาจถูก GC ย้ายตำแหน่งได้ หากต้องการหาที่อยู่ของอ็อบเจ็กต์ดังกล่าว คุณต้องตรึงอ็อบเจ็กต์นั้นด้วย fixed GC จะไม่ย้ายอ็อบเจ็กต์ในช่วงเวลาที่บล็อก fixed ทำงาน

byte[] buffer = new byte[256];

unsafe
{
    fixed (byte* pBuf = buffer)
    {
        // pBuf is valid only inside this block
        // GC won't relocate 'buffer' here
        for (int i = 0; i < buffer.Length; i++)
            pBuf[i] = (byte)i;
    }
    // After 'fixed', GC can move 'buffer' again
}

Console.WriteLine(buffer[5]); // 5

การคำนวณทางพอยน์เตอร์

คุณสามารถบวกหรือลบจำนวนเต็มกับพอยน์เตอร์ได้ การบวก 1 ให้กับ int* จะเลื่อนไปข้างหน้า 4 ไบต์ (ขนาดของ int) นี่คือพื้นฐานของการดำเนินการกับหน่วยความจำปริมาณมากที่รวดเร็ว

unsafe
{
    int[] data = { 10, 20, 30, 40, 50 };
    fixed (int* start = data)
    {
        int* p = start;
        long sum = 0;
        for (int i = 0; i < data.Length; i++)
        {
            sum += *p;
            p++;    // advance by sizeof(int) = 4 bytes
        }
        Console.WriteLine(sum); // 150

        // Or with index syntax:
        Console.WriteLine(start[2]); // 30
    }
}

stackalloc: การจัดสรรบนสแตก

stackalloc จัดสรรบล็อกหน่วยความจำบนสแตก ไม่ใช่ฮีป จึงไม่สร้างภาระให้ GC และไม่จำเป็นต้องตรึงหน่วยความจำ การจัดสรรนี้จำกัดอยู่ในเฟรมสแตกของเมธอดปัจจุบัน และหน่วยความจำจะถูกคืนโดยอัตโนมัติเมื่อเมธอดส่งคืนค่า

// Stack-allocated buffer — zero heap allocation
Span<int> buffer = stackalloc int[128]; // safe Span wrapper
buffer.Fill(0);
buffer[0] = 42;
Console.WriteLine(buffer[0]); // 42

// Or raw pointer form (requires unsafe):
unsafe
{
    int* raw = stackalloc int[128];
    raw[0] = 99;
    Console.WriteLine(raw[0]); // 99
}
// Stack frame popped — memory gone

บัฟเฟอร์ขนาดคงที่ในโครงสร้าง

บัฟเฟอร์ขนาดคงที่จะแทรกอาร์เรย์ความยาวคงที่ไว้ภายในโครงสร้างโดยตรง จึงไม่ต้องจัดสรรบนฮีปและไม่ต้องใช้พอยน์เตอร์เพิ่มเติม ให้ใช้ตัวปรับเปลี่ยน fixed ภายในโครงสร้างที่ประกาศในบริบท unsafe

unsafe struct NetworkPacket
{
    public int  Length;
    public byte Command;
    public fixed byte Payload[256]; // 256 bytes inline in the struct
}

unsafe
{
    NetworkPacket pkt;
    pkt.Length  = 10;
    pkt.Command = 0x01;

    // Fill payload inline — no heap allocation
    for (int i = 0; i < 10; i++)
        pkt.Payload[i] = (byte)i;

    Console.WriteLine(pkt.Payload[5]); // 5
}
// sizeof(NetworkPacket) = 4 + 1 + 256 = ~261 bytes on stack

void* และการแปลงชนิด

void* คือพอยน์เตอร์ที่ไม่มีชนิดข้อมูล เหมาะสำหรับการดำเนินการกับหน่วยความจำทั่วไป คุณต้องแปลงเป็นพอยน์เตอร์ที่มีชนิดข้อมูลก่อนจึงจะสามารถอ่านค่าผ่านพอยน์เตอร์ได้ ตัวดำเนินการ sizeof(T) ใช้กับชนิดข้อมูลแบบไม่มีการจัดการได้ในบริบท unsafe

unsafe
{
    int value = 42;
    void* vp = &value;      // typeless pointer
    int*  ip = (int*)vp;    // cast back to int*
    Console.WriteLine(*ip); // 42

    // sizeof works on unmanaged types
    Console.WriteLine(sizeof(int));    // 4
    Console.WriteLine(sizeof(double)); // 8
    Console.WriteLine(sizeof(long));   // 8
}

คลาส MemoryMarshal และ Unsafe

System.Runtime.CompilerServices.Unsafe และ System.Runtime.InteropServices.MemoryMarshal มีทางเลือกที่ปลอดภัยและไม่ต้องใช้คีย์เวิร์ดสำหรับการดำเนินการระดับล่างหลายประเภท ทั้งยังเข้ากันได้กับ Span และใช้งานได้โดยไม่ต้องใช้คีย์เวิร์ด unsafe

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// Reinterpret a Span<byte> as Span<int> — no copy, no unsafe keyword
byte[] raw = new byte[16];
Span<int> ints = MemoryMarshal.Cast<byte, int>(raw);
ints[0] = 12345678;
Console.WriteLine(ints[0]); // 12345678

// Unsafe.As<,> — reinterpret reference type (advanced)
ref int first = ref MemoryMarshal.GetReference(ints);
Unsafe.Add(ref first, 1) = 99999;
Console.WriteLine(ints[1]); // 99999

ควร (หรือไม่ควร) ใช้โค้ด unsafe เมื่อใด

ใช้โค้ด unsafe เฉพาะเมื่อมีความจำเป็นด้านประสิทธิภาพอย่างชัดเจนและไม่มีทางเลือกที่ปลอดภัย รักษาขอบเขตของโค้ด unsafe ให้เล็กที่สุด โดยแยกไว้ในเมธอดตัวช่วยส่วนตัวหรือชนิดข้อมูลเฉพาะ เขียนการทดสอบอย่างครอบคลุมและบันทึกสมมติฐานต่าง ๆ ไว้

// GOOD: narrow unsafe scope
public static unsafe int SumBytes(ReadOnlySpan<byte> data)
{
    int total = 0;
    fixed (byte* p = data)
    {
        byte* end = p + data.Length;
        for (byte* cur = p; cur < end; cur++)
            total += *cur;
    }
    return total;
}

// BETTER for modern code: use SIMD via Vector<T> or hardware intrinsics
// which stay in managed code but still achieve native-level perf

ตัวอย่างจากการใช้งานจริง: การแยกวิเคราะห์โพรโทคอลไบนารี

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

[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct MessageHeader
{
    public uint  Magic;        // 4 bytes
    public ushort Version;    // 2 bytes
    public ushort PayloadLen; // 2 bytes
    public fixed byte Id[16]; // 16 bytes GUID inline
}

unsafe ReadOnlySpan<byte> SerializeHeader(MessageHeader hdr)
{
    // Cast struct directly to bytes — zero copy
    return new ReadOnlySpan<byte>(&hdr, sizeof(MessageHeader));
}

unsafe MessageHeader ParseHeader(ReadOnlySpan<byte> buf)
{
    fixed (byte* p = buf)
        return *(MessageHeader*)p; // reinterpret cast
}

ตรวจสอบความเข้าใจ

เหตุใดจึงต้องใช้คำสั่ง fixed เมื่อต้องการหาที่อยู่ของอ็อบเจ็กต์ที่จัดการโดย .NET

ทบทวน: โค้ด unsafe พอยน์เตอร์ และบัฟเฟอร์ขนาดคงที่

ประเด็นสำคัญ:

  • คีย์เวิร์ด unsafe เปิดให้ใช้พอยน์เตอร์ดิบได้ และต้องเปิดใช้ AllowUnsafeBlocks ในโครงการ
  • คำสั่ง fixed จะตรึงอ็อบเจ็กต์ที่จัดการโดย .NET เพื่อป้องกันไม่ให้ GC ย้ายตำแหน่ง
  • การคำนวณทางพอยน์เตอร์จะเลื่อนไปตามขนาดของสมาชิก ไม่ใช่จำนวนไบต์
  • stackalloc จัดสรรหน่วยความจำบนสแตก ไม่สร้างภาระให้ GC และถูกคืนเมื่อเมธอดสิ้นสุด
  • บัฟเฟอร์ขนาดคงที่ (fixed byte Payload[N]) จะแทรกอาร์เรย์ไว้ภายในโครงสร้างโดยตรง
  • ในสถานการณ์ส่วนใหญ่ ควรเลือกใช้คลาส Span<T>, MemoryMarshal และ Unsafe

คำถามที่พบบ่อย

บทเรียน “โค้ดไม่ปลอดภัย พอยน์เตอร์ และบัฟเฟอร์ขนาดคงที่” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “โค้ดไม่ปลอดภัย พอยน์เตอร์ และบัฟเฟอร์ขนาดคงที่” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “โค้ดไม่ปลอดภัย พอยน์เตอร์ และบัฟเฟอร์ขนาดคงที่”

ใช้คีย์เวิร์ด unsafe ทำงานกับพอยน์เตอร์ ตรึงหน่วยความจำที่มีการจัดการด้วย fixed และเข้าถึงบัฟเฟอร์ขนาดคงที่ในโครงสร้าง คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “โค้ดไม่ปลอดภัย พอยน์เตอร์ และบัฟเฟอร์ขนาดคงที่” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม

ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. พื้นฐาน P/Invoke
  2. LibraryImport และ P/Invoke ที่สร้างจากซอร์ส
  3. โค้ดไม่ปลอดภัย พอยน์เตอร์ และบัฟเฟอร์ขนาดคงที่
  4. การทำงานร่วมกันระหว่าง COM และตัวห่อที่เรียกใช้ได้ขณะรันไทม์
← กลับไปที่ C# Academy