El autor, cuyo primer lenguaje de sistemas fue Rust, señala que C no tenía un tipo booleano primitivo hasta el estándar C23; antes de eso, los programadores tenían que incluir <stdbool.h> para obtener los macros true y false, que simplemente se expanden a las constantes enteras 1 y 0. Esto significa que, a diferencia del bool incorporado de Rust, el código C temprano trataba los booleanos como enteros ordinarios, un hecho que puede sorprender a los desarrolladores que vienen de lenguajes donde true y false son literales distintos. El post observa que incluso después de que C23 hiciera que los booleanos fueran un primitivo verdadero del lenguaje, los compiladores más antiguos aún requieren el encabezado para una compatibilidad completa.

Las cadenas en C están terminadas en nulo en lugar de estar prefijadas con longitud. Una cadena en C es simplemente un puntero a su primer carácter, y su longitud se determina escaneando el byte cero (⟨0⟩) que marca el final. Como resultado, cada cadena debe reservar un byte adicional para ese terminador, y olvidarse de agregarlo—or calcular mal la asignación—puede causar lecturas fuera de los límites cuando funciones como strcpy o strlen se ejecutan más allá del búfer previsto. Como se muestra en la imagen IMG-001, los desbordamientos de búfer y las escrituras fuera de los límites son categorías comunes de vulnerabilidades en C. El artículo ilustra el punto con una rutina manual de reversión de cadenas que asigna explícitamente len+1 bytes y escribe el terminador en el índice len, mostrando el control de registros que Rust’s &str (que almacena la longitud junto al puntero) evita.

Gráfico de barras que muestra categorías de vulnerabilidad con desbordamiento de búfer y escrituras fuera de los límites destacados
Los desbordamientos de búfer y las escrituras fuera de los límites son categorías comunes de vulnerabilidades en Cbd103.dev

Esta imagen ilustra la prevalencia de los desbordamientos de búfer y las escrituras fuera de los límites, que se relacionan con la gestión manual de memoria y la falta de comprobación de límites de C discutida en el artículo.

Los tipos de enteros de C no son de ancho fijo; su tamaño exacto depende de la plataforma objetivo. Por ejemplo, long es de 64 bits en Unix pero 32 bits en Windows, mientras que int es uniformemente de 32 bits en ambos pero solo garantizado para ser al menos de 16 bits por el estándar. Esta variabilidad hace que el código portátil sea frágil a menos que los desarrolladores usen los alias de ancho fijo en <stdint.h> (uint32_t, int64_t, etc.). El post también destaca el manejo mínimo de errores de C: muchas funciones indican fallo devolviendo un valor centinela como -1 o un puntero nulo, y errno puede contener un código local de subproceso para más detalle, pero no hay un mecanismo del lenguaje que obligue al llamante a verificar el resultado. Olvidarse de probar el valor de retorno de malloc, por ejemplo, conduce a una falla de segmentación cuando el puntero nulo se desreferencia, ilustrando cómo C coloca la carga de la seguridad en el programador.