C2y elimina 45 comportamientos indefinidos: el estándar C se libera de sus 'demonios'

El compilador puede hacer literalmente cualquier cosa ante comportamiento indefinido
Explicación de cómo C89 permitió flexibilidad que se convirtió en una vulnerabilidad de seguridad histórica.
Mark

¿Por qué sigue siendo importante C en 2026 si existen lenguajes más seguros?

Mimi

C es portable, compila rápido, produce binarios rápidos y es fácil entender qué va a hacer la computadora cuando lees el código. Pero más importante: corre en el kernel de tu sistema operativo, en tu base de datos, en el firmware del freno de tu auto. No puedes escapar de C aunque lo intentes.

Luke

Pero eso no responde por qué alguien escribiría C nuevo hoy. ¿No es más bien que C es una deuda técnica histórica que heredamos?

Mimi

Parcialmente sí. Pero hay capas donde C sigue siendo la opción correcta: firmware embebido, drivers de hardware, sistemas con restricciones de tiempo real. Rust no puede reemplazar eso todavía.

Mark

Entonces el comportamiento indefinido es el problema real. ¿Qué es exactamente?

Mimi

Es cuando tu programa hace algo que el estándar no define. El compilador puede hacer literalmente cualquier cosa. Puede eliminar comprobaciones de seguridad, reordenar código, asumir que ciertos casos nunca ocurren.

Luke

Pero eso suena como un defecto del estándar, no del lenguaje. ¿Por qué C89 permitió esto?

Mimi

Porque en 1989 había hardware radicalmente diverso. Máquinas con bytes de 9 bits, representaciones de enteros exóticas. El comité necesitaba flexibilidad para que C funcionara en todas partes.

Mark

¿Y C2y lo resuelve?

Mimi

Elimina 45 de los 100 comportamientos indefinidos. Pero no es una solución completa. Aún quedan 55, y los desacuerdos entre compiladores persisten incluso cuando el estándar es claro.

Luke

¿Qué tan grave es esto en producción? ¿Cuántos bugs reales causa?

Mimi

Las vulnerabilidades de memoria en C siguen siendo una de las fuentes más comunes de CVE. En fintech, IoT, healthtech y automotriz, esto es crítico.

Mark

¿Qué pueden hacer las startups ahora?

Mimi

Activar sanitizers en CI/CD, configurar los warnings del compilador al máximo, auditar código crítico con analizadores estáticos. Las herramientas ya existen.

Luke

¿Y eso es suficiente?

Mimi

Para la mayoría de casos, sí. Para seguridad de memoria total, necesitarías verificación formal, que es costosa. Pero la mayoría de bugs se atrapan con sanitizers.

  • C2y eliminó 45 de los 100 comportamientos indefinidos que C arrastraba desde C89
  • C23 prohibió optimizaciones peligrosas como mover operaciones en el tiempo
  • Vulnerabilidades de memoria en C siguen siendo fuente común de CVE en producción
  • Sanitizers, analizadores estáticos y verificación formal ya están disponibles para proteger código

C2y reduce de 100 a 55 los comportamientos indefinidos ('demonios') que permitían al compilador hacer cualquier cosa ante código no portable. C23 ya prohibió optimizaciones peligrosas como mover operaciones en el tiempo; herramientas como sanitizers, analizadores estáticos y verificación formal ya están disponibles.

El borrador C2y del estándar C eliminó 45 de los 100 comportamientos indefinidos históricos, mejorando la seguridad de memoria en un lenguaje que sigue siendo crítico en sistemas embebidos, kernels y dispositivos médicos.

El lenguaje C lleva más de medio siglo en movimiento constante. Corre en el kernel de tu sistema operativo, en las bases de datos que almacenan transacciones, en el firmware que controla los frenos de tu auto y en el termostato inteligente que instalaste hace poco. Casi cualquier dispositivo en Latinoamérica —desde una terminal de punto de venta hasta un monitor cardíaco certificado— tiene C en alguna capa de su arquitectura. Y durante todo ese tiempo, ha cargado con un problema que sus creadores nunca pudieron resolver del todo: el comportamiento indefinido.

Martin Uecker, miembro del comité ISO/IEC WG14 que estandariza C, presentó en Kernel Recipes 2026 un balance que marca un punto de inflexión. El borrador C2y del estándar ha eliminado 45 de los aproximadamente 100 comportamientos indefinidos que el lenguaje arrastraba desde C89. Junto con esa noticia, Uecker compartió un mapa de herramientas que los equipos de desarrollo pueden activar hoy mismo para protegerse de los 55 casos que aún permanecen.

Para entender por qué esto importa, hay que retroceder a 1989. El comité que escribió C89 enfrentaba un desafío brutal: hardware radicalmente diverso. Máquinas con representación de enteros en signo-magnitud, otras en complemento a uno, memoria segmentada, punteros exóticos y hasta bytes de 9 bits en algunas computadoras Honeywell. Para escribir un estándar que funcionara en todas partes, el comité definió la semántica en términos de una máquina abstracta ideal. Los programas debían comportarse como si se ejecutaran en esa máquina teórica, no en el hardware real. El problema surge cuando un programa hace algo que no es portable o que el estándar no define. C89 lo llamó «comportamiento indefinido» y declaró que no imponía ningún requisito sobre cómo el compilador debería manejarlo. Esto parecía una solución elegante: permitía extensiones del compilador, soporte para mecanismos de seguridad del hardware y optimizaciones agresivas. Pero la realidad fue más oscura.

La comunidad de programadores resumió el problema con una frase que se quedó: «demonios nasales». Si tu programa contiene cualquier comportamiento indefinido, un compilador puede interpretarlo como que el programa entero carece de semántica esperada. Puede hacer literalmente cualquier cosa. El caso clásico es la división por cero. Si escribes código que divide entre una variable que podría ser cero, algunos compiladores modernos eliminan la comprobación completa y asumen que ese caso nunca ocurrirá, ejecutando solo el código que viene después. Esto no es teoría académica: ocurre en compiladores reales, en producción, todos los días. Otro bug famoso es lo que Uecker llamó el «viaje en el tiempo»: el compilador mueve operaciones hacia atrás en el tiempo, reordenando código de formas que parecen imposibles. C23 introdujo una estipulación explícita para prohibirlo, pero en C++ el programador debe insertar manualmente una llamada a std::observable_checkpoint().

La mejora que trae C2y es significativa pero no mágica. El comité de C mantiene tres grupos de estudio dedicados específicamente al modelo de objetos en memoria, seguridad de memoria y comportamiento indefinido. Eliminaron 45 demonios, pero los desacuerdos entre implementadores de compiladores persisten incluso cuando el estándar es claro. Tanto Clang como GCC han compilado mal código que el estándar define con precisión, especialmente en casos de lectura de variables no inicializadas y comparación de igualdad de punteros.

Para protegerse ahora, sin esperar a C2y, existe un ecosistema de herramientas cada vez más rico. Los compiladores han mejorado sus avisos para overflow de enteros y posibles casos de use-after-free. Los analizadores estáticos, disponibles como herramientas independientes o integrados en compiladores, ya advierten de buffer overflow. Los sanitizers insertan comprobaciones en tiempo de ejecución y pueden atrapar mucho comportamiento indefinido. Existe verificación formal, la más completa pero también la más costosa. Y una categoría nueva que el comité sigue con atención: herramientas basadas en modelos de lenguaje.

La pregunta que importa para startups es si C puede llegar a seguridad de memoria total. Uecker fue honesto: no completamente, no sin un costo. La seguridad de tipos es relativamente fuerte en C; las uniones sin etiqueta pueden crear confusión, pero el compilador puede reforzar tipos con anotaciones. La seguridad espacial, el bounds checking, está parcialmente resuelta; los compiladores ya hacen comprobación de límites en muchos casos y el atributo counted_by permite comprobación en miembros de array flexibles. La seguridad temporal, evitar use-after-free, es el punto más débil de C, donde Rust tiene una ventaja clara. Aun así, arquitecturas como CHERI ayudan, y herramientas como Fil-C pueden encontrar muchos bugs de seguridad temporal. Resolverlo por completo requerirá comprobaciones costosas en tiempo de ejecución o verificación formal. En el futuro cercano, los mejores resultados vendrán de combinar lenguaje restringido con verificación formal.

Para startups en Latinoamérica y España que construyen en fintech, IoT, healthtech o automotriz, esto no es un debate académico. Las vulnerabilidades de memoria en C siguen siendo una de las fuentes más comunes de CVE en software de producción. Una terminal de pagos con un bug de memoria es un riesgo PCI-DSS. Un dispositivo IoT con una vulnerabilidad de seguridad temporal vive en campo sin posibilidad de parche frecuente. Un dispositivo médico con un fallo de bounds checking enfrenta regulaciones europeas cada vez más estrictas. Cualquier producto que toque CAN bus o ADAS vive en C con memoria compartida y restricciones de tiempo real. La conclusión operativa es clara: el comité está haciendo el trabajo difícil en el estándar, pero las herramientas para proteger el código ya están disponibles ahora. No hay que esperar a C2y. Hay que encender los sanitizers esta semana.

C es portable, compila rápido, produce binarios rápidos y es fácil entender qué va a hacer la computadora cuando lees el código
— Martin Uecker, comité ISO/IEC WG14
Resolverlo por completo requerirá comprobaciones costosas en tiempo de ejecución o verificación formal
— Martin Uecker, sobre seguridad de memoria total en C
Contact Us FAQ