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]); // 5Arytmetyka 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 goneBufory 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 stackvoid* 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]); // 99999Kiedy 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 perfPraktyczny 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
unsafeumożliwia używanie surowych wskaźników — wymaga ustawieniaAllowUnsafeBlocksw projekcie - Instrukcja
fixedprzypina 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
stackallocalokuje 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>,MemoryMarshaliUnsafe
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
- Podstawy P/Invoke
- LibraryImport i generowane P/Invoke
- Kod niebezpieczny, wskaźniki i bufory o stałym rozmiarze
- Interop COM i opakowania wywoływalne ze środowiska uruchomieniowego