0Pricing
C# Academy · Lekcja

Kod niebezpieczny, wskaźniki i bufory o stałym rozmiarze

Proszę użyć słowa kluczowego unsafe, pracować ze wskaźnikami, przypinać zarządzaną pamięć za pomocą fixed oraz uzyskiwać dostęp do buforów o stałym rozmiarze w strukturach.

Kod niebezpieczny, wskaźniki i bufory o stałym rozmiarze to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.

Czym jest kod unsafe?

Słowo kluczowe unsafe w języku C# umożliwia bezpośrednią manipulację pamięcią: używanie surowych wskaźników, arytmetykę wskaźników i bufory o stałym rozmiarze. Omija gwarancje bezpieczeństwa zapewniane przez GC. Należy je włączyć za pomocą <AllowUnsafeBlocks>true</AllowUnsafeBlocks> w pliku projektu.

// 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
}

Typy wskaźnikowe w języku C#

Składnia wskaźników przypomina składnię C/C++: T* oznacza wskaźnik do typu T. Można deklarować wskaźniki do niezarządzanych typów wartości (int, double oraz struktur niezawierających pól referencyjnych). Wskaźniki do typów zarządzanych nie są dozwolone.

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
    }
}

Instrukcja fixed

Obiekty zarządzane mogą być przenoszone przez GC. Aby pobrać adres zarządzanego obiektu, należy go przypiąć za pomocą fixed. GC nie przeniesie obiektu przez cały czas wykonywania bloku 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

Arytmetyka wskaźników

Do wskaźników można dodawać liczby całkowite lub je od nich odejmować. Dodanie 1 do wskaźnika int* przesuwa go o 4 bajty (sizeof int). Jest to podstawa szybkich operacji na dużych blokach pamięci.

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: alokacja na stosie

stackalloc alokuje blok pamięci na stosie, a nie na stercie. Nie powoduje obciążenia GC i nie wymaga przypinania. Alokacja jest ograniczona do bieżącej ramki stosu metody — pamięć zostaje automatycznie zwolniona po powrocie z metody.

// 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

Bufory o stałym rozmiarze w strukturach

Bufor o stałym rozmiarze osadza tablicę o stałej długości bezpośrednio w strukturze — bez alokacji na stercie i dodatkowego wskaźnika. Modyfikatora fixed należy użyć wewnątrz struktury zadeklarowanej w kontekście 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* i rzutowanie

void* to wskaźnik bez określonego typu — przydatny podczas wykonywania ogólnych operacji na pamięci. Przed wyłuskaniem wartości należy zrzutować go na wskaźnik typowany. Operator sizeof(T) działa dla typów niezarządzanych w kontekście 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
}

Klasy MemoryMarshal i Unsafe

System.Runtime.CompilerServices.Unsafe i System.Runtime.InteropServices.MemoryMarshal udostępniają bezpieczne, niewymagające słowa kluczowego alternatywy dla wielu operacji niskopoziomowych. Są zgodne z Span i można ich używać bez słowa kluczowego 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

Kiedy używać unsafe, a kiedy nie

Kodu unsafe należy używać tylko wtedy, gdy istnieje wyraźna potrzeba wydajnościowa i nie ma bezpiecznej alternatywy. Zakres kodu unsafe powinien być minimalny — należy izolować go w prywatnych metodach pomocniczych lub dedykowanych typach. Należy pisać wyczerpujące testy i dokumentować przyjęte założenia.

// 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

Praktyczny przykład: analizowanie protokołu binarnego

Wskaźniki unsafe i bufory o stałym rozmiarze są szczególnie przydatne podczas analizowania protokołów binarnych, w których układ struktury musi dokładnie odpowiadać formatowi transmisji. Strukturę można bezpośrednio zrzutować z bufora bajtów bez wykonywania kopii.

[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
}

Szybkie sprawdzenie

Dlaczego instrukcja fixed jest potrzebna podczas pobierania adresu obiektu zarządzanego?

Podsumowanie: kod unsafe, wskaźniki i bufory fixed

Najważniejsze wnioski:

  • Słowo kluczowe unsafe umożliwia używanie surowych wskaźników — wymaga ustawienia AllowUnsafeBlocks w projekcie
  • Instrukcja fixed przypina obiekty zarządzane, aby GC ich nie przeniósł
  • Arytmetyka wskaźników przesuwa wskaźnik o rozmiar elementu, a nie o liczbę bajtów
  • stackalloc alokuje pamięć na stosie — nie obciąża GC i jest zwalniane przy wyjściu z metody
  • Bufory o stałym rozmiarze (fixed byte Payload[N]) osadzają tablice bezpośrednio w strukturach
  • W większości scenariuszy należy preferować klasę Span<T>, MemoryMarshal i Unsafe

Często zadawane pytania

Czy lekcja „Kod niebezpieczny, wskaźniki i bufory o stałym rozmiarze” jest bezpłatna?

Tak — pełny tekst „Kod niebezpieczny, wskaźniki i bufory o stałym rozmiarze” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Kod niebezpieczny, wskaźniki i bufory o stałym rozmiarze”?

Proszę użyć słowa kluczowego unsafe, pracować ze wskaźnikami, przypinać zarządzaną pamięć za pomocą fixed oraz uzyskiwać dostęp do buforów o stałym rozmiarze w strukturach. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć C# Academy?

Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Kod niebezpieczny, wskaźniki i bufory o stałym rozmiarze”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?

Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Podstawy P/Invoke
  2. LibraryImport i generowane P/Invoke
  3. Kod niebezpieczny, wskaźniki i bufory o stałym rozmiarze
  4. Interop COM i opakowania wywoływalne ze środowiska uruchomieniowego
← Powrót do C# Academy