c-sharp-unsage-fixed

C# Unsafe e fixed: quando (e come) scrivere codice low-level in sicurezza

Il linguaggio C# è noto per la sua sicurezza e l’astrazione dai dettagli di basso livello, ma esistono scenari dove serve sporcarsi le mani con la memoria.
In questi casi entrano in gioco le parole chiave unsafe e fixed, strumenti potenti ma spesso fraintesi, che permettono di lavorare con puntatori e memoria nativa — senza rinunciare alla stabilità e alla prevedibilità del runtime .NET.

In questo articolo vedremo quando ha senso usarli, come farlo in modo corretto, e quali sono le best practice per evitare bug e memory leak.

Cosa significa “unsafe” in C#

Il modificatore unsafe indica al compilatore che il codice al suo interno non è soggetto ai controlli di sicurezza tipici del CLR.
Significa che puoi:

  • accedere direttamente a indirizzi di memoria;
  • usare puntatori (int*, byte*, ecc.);
  • manipolare buffer e strutture native;
  • interagire con codice non gestito (C/C++ via interop).

Esempio semplice:

unsafe
{
    int value = 42;
    int* ptr = &value;
    Console.WriteLine(*ptr); // Output: 42
}

Per compilare codice “unsafe”, devi abilitare l’opzione nel progetto (ad esempio in .csproj):

<PropertyGroup>
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Cosa fa il blocco fixed

In C#, gli oggetti gestiti possono essere spostati dal Garbage Collector (GC) in memoria.
Questo è un problema se vogliamo ottenere un puntatore fisso a una variabile gestita.

Il blocco fixed serve proprio a questo: “pinna” l’oggetto in memoria impedendo al GC di muoverlo.

Esempio:

unsafe
{
    int[] data = { 10, 20, 30 };

    fixed (int* ptr = data)
    {
        for (int i = 0; i < data.Length; i++)
            Console.WriteLine(*(ptr + i));
    }
}

In questo modo ptr punta in modo stabile al primo elemento dell’array, anche se il GC effettua una collection.
Uscendo dal blocco fixed, l’oggetto viene “unpinnato”.

Quando ha senso usare unsafe e fixed

Molti sviluppatori evitano l’uso di unsafe, ma in realtà è perfettamente legittimo — purché sia motivato e confinato.
Ecco i casi tipici dove ha senso usarlo:

1 Performance critica su grandi dataset

Se stai processando milioni di elementi (es. immagini, audio, segnali), l’accesso diretto ai buffer può ridurre l’overhead del runtime.

2 Interoperabilità con librerie native

Quando integri C# con librerie C/C++ (via P/Invoke), potresti dover manipolare puntatori o passare buffer nativi.

3 Serializzazione o parsing binario

Lavorare con strutture a layout fisso (struct con [StructLayout(LayoutKind.Sequential)]) e usare fixed può evitare conversioni lente.

4 Engine e sistemi embedded

Motori di gioco, moduli grafici o dispositivi IoT spesso richiedono gestione esplicita della memoria per ottenere performance deterministiche.

Best practice per scrivere codice unsafe sicuro

  1. Isolare il codice unsafe
    Crea metodi o moduli separati e mantieni il resto del codice “safe”. internal static unsafe void Copy(byte* src, byte* dest, int count) { ... }
  2. Non fidarti mai dei buffer in ingresso
    Valida sempre le dimensioni e limiti l’accesso diretto con condizioni (if e bounds check manuali).
  3. Usa stackalloc quando possibile
    Alloca piccoli buffer sullo stack (non sull’heap), evitando il rischio di leak. Span<byte> buffer = stackalloc byte[256];
  4. Evita pinning prolungato
    Bloccare troppi oggetti in memoria riduce l’efficienza del GC. Usa fixed solo per il tempo strettamente necessario.
  5. Preferisci Span<T> e Memory<T> quando puoi
    Dal C# 7.2 in poi, molti casi d’uso di unsafe possono essere sostituiti da Span<T> che offre performance simili ma con sicurezza.

Alternative moderne a unsafe

Oggi, molte delle operazioni “a basso livello” possono essere realizzate senza codice non sicuro, grazie a:

  • Span<T> e ReadOnlySpan<T>
  • MemoryMarshal
  • ArrayPool<T>
  • Memory<T> e IMemoryOwner<T>

Queste API offrono performance comparabili ma con la protezione del runtime.
unsafe rimane l’ultima opzione quando serve controllo totale e prevedibilità assoluta.

Conclusione

Scrivere codice unsafe in C# non significa scrivere codice pericoloso.
Significa semplicemente assumersi consapevolmente la responsabilità della memoria, quando il contesto lo richiede — come fanno i sistemi ad alte prestazioni, i game engine o i runtime custom.

Usato bene, unsafe è uno strumento che ti permette di spingere C# oltre i suoi limiti
senza uscire dal perimetro della sicurezza e della qualità del codice.

1 È possibile usare unsafe in un progetto Blazor o ASP.NET Core?
Sì, ma è raro e sconsigliato. Gli scenari server-side non traggono beneficio dal codice pointer-based, salvo per integrazioni native specifiche.

2 L’uso di unsafe disattiva il Garbage Collector?
No. Il GC continua a funzionare normalmente; l’unica differenza è che può ignorare alcune aree “pinnate” durante la collection.

3 Posso combinare unsafe con Span<T>?
Sì, ma con attenzione: puoi ottenere uno Span<T> da un puntatore usando MemoryMarshal.CreateSpan, utile in interoperabilità.

4 unsafe rallenta la compilazione o l’ottimizzazione JIT?
Al contrario: il JIT può spesso ottimizzare meglio codice pointer-based, specialmente in loop intensivi.

Approfondimenti consigliati


Pubblicato

in

da

Tag: