Sanitizers: cazar bugs en ejecución
ASan, UBSan, MSan y TSan instrumentan tu programa para detectar en el acto los errores de memoria, el comportamiento indefinido y las condiciones de carrera. Imprescindibles.
Los cuatro pecados de la memoria (nivel 13) y el Undefined Behavior fallan de forma no determinista: a veces, en otro sitio, más tarde. Los sanitizers cambian las reglas: instrumentan tu programa para que esos errores exploten en el instante y el lugar exactos en que ocurren. Son la herramienta de calidad más importante del C moderno.
- Qué es un sanitizer y cómo funciona.
- AddressSanitizer y UndefinedBehaviorSanitizer.
- MemorySanitizer y ThreadSanitizer.
- Integrarlos en tu flujo de trabajo.
Qué son y cómo funcionan
Un sanitizer es una instrumentación que el compilador inserta en tu binario: código extra que vigila cada acceso a memoria, cada operación aritmética o cada acceso concurrente, y aborta con un informe preciso en cuanto detecta algo mal. Se activan al compilar y ralentizan el programa (por eso son para desarrollo y CI, no para producción).
AddressSanitizer (ASan)
El rey. Detecta la mayoría de errores de memoria: buffer overflow, use-after-free, double free, y fugas.
gcc -std=c23 -fsanitize=address -g prog.c -o prog
./prog
# ante un error, imprime: tipo exacto, la línea del acceso ilegal,
# dónde se reservó y dónde se liberó esa memoria. Un mapa completo del bug.
La diferencia que ASan supone es difícil de exagerar. Sin él, un use-after-free se manifiesta como datos corruptos que provocan un crash páginas más tarde, en una función que no tiene nada que ver — horas de depuración a ciegas. Con ASan, en el momento exacto en que tocas la memoria liberada, el programa se detiene y te dice: “use-after-free en la línea 42; esta memoria se reservó en la línea 10 y se liberó en la línea 30”. El bug pasa de fantasma inatrapable a informe con dirección postal. Ejecuta tus tests bajo ASan siempre: encontrarás bugs que ni sabías que tenías, antes de que lleguen a producción. Es, sin discusión, la práctica de mayor retorno para la calidad de código C.
UndefinedBehaviorSanitizer (UBSan)
Detecta Undefined Behavior en el acto: desbordamiento de enteros con signo, desplazamientos inválidos, dereferencias nulas, punteros mal alineados, violaciones de aliasing.
gcc -std=c23 -fsanitize=undefined -g prog.c -o prog
# combinado con ASan (compatibles entre sí):
gcc -std=c23 -fsanitize=address,undefined -g prog.c -o prog
UBSan es tu red contra los bugs sutiles del nivel 29. Barato y compatible con ASan: actívalos juntos.
MemorySanitizer y ThreadSanitizer
MemorySanitizer (MSan)
Detecta lecturas de memoria no inicializada — usar una variable antes de darle valor. Solo en Clang.
ThreadSanitizer (TSan)
Detecta condiciones de carrera: dos hilos accediendo a la misma memoria sin sincronizar. Imprescindible para la concurrencia (nivel 27).
ASan y UBSan se llevan bien y se activan juntos. Pero TSan y MSan no se combinan con ASan (usan el mismo mecanismo de sombra de memoria). El flujo típico: un build con address,undefined para el día a día, y builds separados con thread (cuando trabajes concurrencia) o memory. Aquí es donde los directorios de build paralelos de Meson (nivel 17) brillan: un build-asan/, un build-tsan/, cada uno con su configuración.
Intégralos en tu flujo
La práctica profesional: corre tu suite de tests bajo -fsanitize=address,undefined en integración continua. Cada test que pase limpio bajo ASan/UBSan es un test que garantiza ausencia de errores de memoria y UB en ese camino de código. Muchos proyectos serios (curl, SQLite, el propio kernel con KASAN) no aceptan un cambio que no pase bajo sanitizers. Es la forma de que la gestión manual de memoria de C sea segura en la práctica: no evitando los errores por pura disciplina —que falla—, sino cazándolos automáticamente antes de fusionar.
- Reproduce los cuatro pecados del nivel 13 (leak, double free, use-after-free, overflow) y córrelos bajo
-fsanitize=address. Lee los informes. - Provoca un desbordamiento de entero con signo y cázalo con
-fsanitize=undefined. - Combina
address,undefinedy confirma que ambos actúan. - Añade un build con sanitizers a tu
meson.buildo Makefile para tenerlo siempre a mano.