0Pricing
C# Academy · Урок

Небезопасный код, указатели и буферы фиксированного размера

Используйте ключевое слово unsafe, работайте с указателями, фиксируйте управляемую память с помощью fixed и обращайтесь к буферам фиксированного размера в структурах.

«Небезопасный код, указатели и буферы фиксированного размера» — бесплатный урок C# Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Что такое небезопасный код

Ключевое слово unsafe в C# открывает доступ к прямому управлению памятью: необработанным указателям, арифметике указателей и буферам фиксированного размера. Оно обходит гарантии безопасности сборщика мусора. Включите его с помощью <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, структуры без ссылочных полей). Указатели на управляемые типы не разрешены.

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

Управляемые объекты могут перемещаться сборщиком мусора. Чтобы получить адрес управляемого объекта, необходимо закрепить его с помощью fixed. Сборщик мусора не переместит объект на протяжении блока 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 выделяет блок памяти в стеке, а не в куче. Нет нагрузки на сборщик мусора и не требуется закрепление. Память ограничена стековым кадром текущего метода и автоматически освобождается при его возврате.

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

Используйте небезопасный код только при очевидной необходимости повысить производительность и отсутствии безопасной альтернативы. Сводите область небезопасного кода к минимуму — изолируйте его в закрытых вспомогательных методах или специализированных типах. Пишите исчерпывающие тесты и документируйте свои допущения.

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

Практический пример: разбор двоичного протокола

Небезопасные указатели и буферы фиксированного размера особенно полезны при разборе двоичных протоколов, где размещение полей структуры должно точно соответствовать формату передачи. Структуру можно directly привести из буфера байтов без единого копирования.

[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 при получении адреса управляемого объекта?

Повторение: небезопасный код, указатели и буферы fixed

Основные выводы:

  • Ключевое слово unsafe открывает доступ к необработанным указателям — в проекте требуется AllowUnsafeBlocks
  • Оператор fixed закрепляет управляемые объекты, чтобы сборщик мусора не переместил их
  • Арифметика указателей перемещает указатель на размер элемента, а не на число байтов
  • stackalloc выделяет память в стеке — без нагрузки на сборщик мусора; память освобождается при выходе из метода
  • Буферы фиксированного размера (fixed byte Payload[N]) встраивают массивы непосредственно в структуры
  • В большинстве случаев предпочитайте класс Span<T>, MemoryMarshal и Unsafe

Часто задаваемые вопросы

Урок «Небезопасный код, указатели и буферы фиксированного размера» бесплатный?

Да — полный текст урока «Небезопасный код, указатели и буферы фиксированного размера» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «Небезопасный код, указатели и буферы фиксированного размера»?

Используйте ключевое слово unsafe, работайте с указателями, фиксируйте управляемую память с помощью fixed и обращайтесь к буферам фиксированного размера в структурах. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 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