0

Manipulación de Memoria en C# 1: Punteros y el contexto unsafe

Por defecto, C# es un lenguaje de «memoria segura» donde el Garbage Collector (GC) es el rey. Sin embargo, para escenarios de alto rendimiento, interoperabilidad con librerías de C++ o procesamiento masivo de imágenes/señales, necesitamos saltarnos las reglas.

El Bloque unsafe y la Configuración del Proyecto

No puedes simplemente escribir punteros en C#. Primero, debes autorizar al compilador. En tu archivo .csproj, asegúrate de tener:

<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

Luego, cualquier método que use punteros debe estar marcado con la keyword unsafe.

Tipos de Punteros y Operadores Críticos

En C#, los punteros solo pueden apuntar a tipos no administrados (sbyte, byte, short, int, long, char, float, double, decimal, bool, enum o structs que solo contengan estos tipos).

Operadores Esenciales:

  • & (Dirección de): Obtiene la dirección de memoria de una variable.
  • * (Indirección): Accede al valor almacenado en esa dirección.
  • -> (Acceso a miembros): Para structs, similar a C++.

El Guardián: La sentencia fixed

El mayor peligro en C# es que el Garbage Collector puede mover objetos de lugar en la memoria para compactarla. Si tienes un puntero apuntando a una dirección y el GC mueve el objeto, tu puntero ahora apunta a basura.

La sentencia fixed «ancla» el objeto en la memoria temporalmente.

unsafe void ManipularArray(int[] datos)
{
    // 'fixed' evita que el GC mueva el array mientras trabajamos
    fixed (int* p = datos)
    {
        // p apunta al primer elemento
        for (int i = 0; i < datos.Length; i++)
        {
            // Aritmética de punteros pura
            *(p + i) += 10; 
        }
    } 
    // Al salir del bloque, el objeto vuelve a ser movible por el GC
}

Aritmética de Punteros: Rendimiento Extremo

¿Por qué usar punteros en lugar de un simple indexador array[i]? La respuesta es el Bounds Checking. Cada vez que accedes a un array de forma normal, .NET verifica que no te salgas del límite. Con punteros, esa validación desaparece, ganando ciclos de CPU vitales.

Ejemplo: Swap de valores sin variables temporales

public unsafe void FastSwap(int* a, int* b)
{
    if (a == b) return;
    *a = *a ^ *b;
    *b = *a ^ *b;
    *a = *a ^ *b;
}

Escenario de «Falla y Error»: ¿Qué pasa si no usas fixed?

Imagina que estás procesando un buffer de audio de 1GB. Obtienes el puntero a la dirección 0x1234. En medio de tu procesamiento, el GC se activa, ve que hay fragmentación y mueve tu buffer a la dirección 0x5678.

  • Resultado: Tu puntero sigue leyendo de 0x1234, que ahora podría contener datos de una tarjeta de crédito o simplemente instrucciones ejecutables.

Consecuencia: El sistema operativo mata el proceso inmediatamente por violación de acceso. Nunca manipules

Comparativa: Memoria Administrada vs Unsafe

CaracterísticaCódigo Seguro (Safe)Código No Seguro (Unsafe)
GestiónAutomática (GC)Manual (Programador)
SeguridadA prueba de fugas de memoriaRiesgo de desbordamiento (Buffer Overflow)
VelocidadAlta (Optimizada por JIT)Máxima (Cerca del metal)
Uso PrincipalAplicaciones Empresariales / WebMotores de Juego / Criptografía / Drivers

Notas

  1. Aísla el Riesgo: Encapsula el código unsafe en clases pequeñas y bien testeadas. No permitas que el código «sucio» se filtre a tu lógica de negocio.
  2. Prefiere Span<T>: Antes de saltar a punteros en 2026, evalúa si Span<T> o Memory<T> resuelven tu problema. Ofrecen un rendimiento cercano a los punteros pero con la seguridad de tipos de C#.
  3. Stackalloc: Usa stackalloc para arrays pequeños y temporales. Esto asigna memoria en el Stack (pila), lo cual es instantáneo y no genera trabajo para el Garbage Collector.
int* buffer = stackalloc int[100]; // Memoria ultrarrápida que muere al terminar el método
  1. Cuidado con el 64-bit: Recuerda que en arquitecturas de 64 bits, un puntero ocupa 8 bytes. Tenlo en cuenta al calcular offsets de memoria manualmente.

Conclusiones

Escribir código en C# ya no es una discusión binaria entre Safe vs Unsafe. Es una decisión arquitectónica consciente.

La memoria administrada del runtime moderno de .NET con un GC altamente optimizado, mejoras en el JIT y estructuras como Span<T> ofrece un rendimiento extraordinario para el 95% de los escenarios empresariales. Para la mayoría de las aplicaciones web, APIs, sistemas distribuidos y soluciones SaaS (como los que solemos diseñar en entornos de Clean Architecture y microservicios), el código seguro no solo es suficiente: es la opción correcta.

Fernando Sonego

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *