0Pricing
C# Academy · Lezione

Codice non sicuro, puntatori e buffer fissi

Utilizzi la parola chiave unsafe, lavori con i puntatori, blocchi la memoria gestita con fixed e acceda ai buffer di dimensione fissa nelle struct.

Codice non sicuro, puntatori e buffer fissi è una lezione C# Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento C# Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso C# Academy include 4 lezioni in totale.

Che cos'è il codice unsafe

La parola chiave unsafe di C# consente la manipolazione diretta della memoria: puntatori grezzi, aritmetica dei puntatori e buffer di dimensione fissa. Elude le garanzie di sicurezza del GC. Per abilitarla, usare <AllowUnsafeBlocks>true</AllowUnsafeBlocks> nel file di progetto.

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

Tipi puntatore in C#

La sintassi dei puntatori ricalca quella di C/C++: T* è un puntatore a T. È possibile dichiarare puntatori a tipi valore non gestiti (int, double, struct senza campi di riferimento). I puntatori a tipi gestiti non sono consentiti.

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

L'istruzione fixed

Gli oggetti gestiti possono essere spostati dal GC. Per ottenere l'indirizzo di un oggetto gestito, è necessario sottoporlo a pinning con fixed. Il GC non sposterà l'oggetto per tutta la durata del blocco 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

Aritmetica dei puntatori

È possibile sommare o sottrarre numeri interi ai puntatori. Aggiungere 1 a un int* lo fa avanzare di 4 byte (sizeof int). Questo è alla base delle operazioni veloci sulla memoria in blocco.

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: allocazione nello stack

stackalloc alloca un blocco di memoria nello stack, non nell'heap. Non comporta pressione sul GC e non richiede il pinning. È limitato al frame dello stack del metodo corrente; la memoria viene liberata automaticamente quando il metodo restituisce il controllo.

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

Buffer di dimensione fissa nelle struct

Un buffer di dimensione fissa incorpora un array di lunghezza fissa direttamente in una struct: non richiede allocazioni sull'heap né un puntatore aggiuntivo. Usare il modificatore fixed all'interno di una struct dichiarata in un contesto 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* e conversioni di tipo

Un void* è un puntatore senza tipo, utile per le operazioni generiche sulla memoria. Prima di dereferenziarlo, è necessario convertirlo in un puntatore tipizzato. L'operatore sizeof(T) funziona sui tipi non gestiti in un contesto 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 e la classe Unsafe

System.Runtime.CompilerServices.Unsafe e System.Runtime.InteropServices.MemoryMarshal forniscono alternative sicure (senza usare parole chiave) per molte operazioni di basso livello, compatibili con Span e utilizzabili senza la parola chiave 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

Quando usare (e non usare) unsafe

Usare il codice unsafe solo quando esiste una chiara necessità di prestazioni e non è disponibile un'alternativa sicura. Mantenere al minimo la superficie unsafe, isolandola in metodi helper privati o in tipi dedicati. Scrivere test esaustivi e documentare le ipotesi.

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

Caso reale: analizzare un protocollo binario

I puntatori unsafe e i buffer di dimensione fissa sono particolarmente utili per analizzare protocolli binari in cui il layout della struct deve corrispondere esattamente al formato trasmesso. È possibile convertire direttamente il buffer di byte nella struct, senza effettuare copie.

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

Verifica rapida

Perché è necessaria l'istruzione fixed quando si vuole ottenere l'indirizzo di un oggetto gestito?

Riepilogo: codice unsafe, puntatori e buffer fissi

Concetti chiave:

  • La parola chiave unsafe abilita i puntatori grezzi e richiede AllowUnsafeBlocks nel progetto
  • L'istruzione fixed sottopone a pinning gli oggetti gestiti affinché il GC non li sposti
  • L'aritmetica dei puntatori avanza in base alla dimensione dell'elemento, non dei byte
  • stackalloc alloca nello stack: non comporta pressione sul GC e la memoria viene liberata all'uscita dal metodo
  • I buffer di dimensione fissa (fixed byte Payload[N]) incorporano gli array direttamente nelle struct
  • Per la maggior parte degli scenari, preferire Span<T>, MemoryMarshal e la classe Unsafe

Domande Frequenti

La lezione «Codice non sicuro, puntatori e buffer fissi» è gratuita?

Sì — il testo completo di «Codice non sicuro, puntatori e buffer fissi» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso C# Academy, passa a CoddyKit PRO. Il corso C# Academy include 4 lezioni in totale.

Cosa imparerò in «Codice non sicuro, puntatori e buffer fissi»?

Utilizzi la parola chiave unsafe, lavori con i puntatori, blocchi la memoria gestita con fixed e acceda ai buffer di dimensione fissa nelle struct. Eserciti C# Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare C# Academy?

Non è richiesta alcuna esperienza precedente. C# Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Codice non sicuro, puntatori e buffer fissi»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione C# Academy?

Sì. Ogni lezione C# Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Fondamenti di P/Invoke
  2. LibraryImport e P/Invoke generato dal codice sorgente
  3. Codice non sicuro, puntatori e buffer fissi
  4. Interop COM e wrapper richiamabili dal runtime
← Torna a C# Academy