Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Regla 0x400Ch: Los formatos de printf/scanf deben coincidir y no ser variables

Archivos y E/S (0x40XX)

Universidad Nacional de Río Negro

0x400Ch: Los formatos de printf/scanf deben coincidir y no ser variables

Enunciado normativo

Los formatos de printf y scanf DEBEN coincidir con sus argumentos y NO DEBEN construirse con datos variables no controlados.

Síntoma en el código del estudiante

La máscara de formato no coincide con el tipo de los argumentos: se imprime un double con %d, se lee un double con %f (en lugar de %lf), se pasa un long a %d, un size_t a %u o un char * a %c. El defecto aparece tanto en printf como en scanf.

Diagnóstico

Mecanismo del defecto

Las funciones variádicas no reciben información de tipos: printf y scanf confían ciegamente en la máscara para saber cuántos bytes y de qué tipo hay que leer de la lista de argumentos. Un double ocupa 8 bytes y suele pasarse en un registro de punto flotante; %d espera un int de 4 bytes en un registro entero. El desajuste hace que la función lea de donde no corresponde.

En scanf el problema es más grave: el especificador determina no solo cuántos bytes leer, sino en qué tipo de puntero escribir. Pasar un float * a %lf hace que scanf escriba 8 bytes donde solo hay 4, pisando memoria contigua.

Consecuencia observable

En printf, la salida muestra valores absurdos (un número enorme, 0, nan) sin ningún error de compilación. En scanf, la escritura con el tamaño equivocado puede corromper variables vecinas en la pila y provocar fallos posteriores difíciles de rastrear. Con las advertencias activadas, el compilador avisa en la mayoría de los casos.

Fundamento en el estándar C11

ISO/IEC 9899:2011 §7.21.6.1 exige que el argumento corresponda al tipo indicado por el especificador de conversión de printf; cualquier otra combinación es comportamiento indefinido. §7.21.6.2 hace lo propio para fscanf, donde además la falta de coincidencia entre el especificador y el tipo apuntado por el argumento también es indefinida.

Corrección idiomática

❌ Código con el antipatrón

double promedio = 7.5;
printf("Promedio: %d\n", promedio);

float nota;
scanf("%f", &nota);      /* correcto para float */

double media;
scanf("%f", &media);     /* incorrecto: %f espera float* */

Por qué es incorrecto: el primer printf interpreta los 8 bytes del double como un int, y el segundo scanf escribe 4 bytes en una variable de 8. Ambos son comportamiento indefinido, aunque el primero solo produzca basura visible.

✅ Código refactorizado

double promedio = 7.5;
printf("Promedio: %f\n", promedio);

float nota;
scanf("%f", &nota);

double media;
scanf("%lf", &media);

Cada especificador coincide con el tipo: %f para double en printf, %f para float y %lf para double en scanf. Para size_t se usa %zu y para long %ld.

Errores típicos al compilar o ejecutar

El compilador detecta el desajuste con las advertencias de formato.

warning: format '%d' expects argument of type 'int',
         but argument 2 has type 'double' [-Wformat=]

En ejecución, un scanf mal tipado puede corromper la pila.

$ ./promedio
Promedio: -927489456
*** stack smashing detected ***: terminated
Aborted (core dumped)

Checklist de verificación

Reglas relacionadas

Síntoma en el código del estudiante

Una variable de tipo char * o char[] se pasa como primer argumento de printf, fprintf, sprintf o snprintf, sin una máscara de formato literal intermedia. Suele verse en funciones de aviso o de impresión de mensajes recibidos.

Diagnóstico

Mecanismo del defecto

El primer argumento de printf no es “texto a imprimir”, sino la cadena de formato: la función la recorre buscando especificadores (%d, %x, %s, %n). Si ese argumento proviene del usuario, el usuario controla el formato. Con %x o %p puede hacer que printf interprete como argumentos valores que quedaron en los registros o en la pila, y así leer memoria privada del proceso. Con %n puede incluso escribir en una dirección de memoria, porque %n guarda la cantidad de caracteres impresos en el puntero que recibe como argumento. Es la vulnerabilidad clásica de format string.

Aunque en el ámbito de la cátedra no haya un atacante, el mismo mecanismo se activa por accidente: un mensaje con un % suelto hace que printf espere un argumento que no existe y lea basura.

Consecuencia observable

Si el texto recibido contiene %s, %x o %n, la salida muestra valores inesperados, el programa imprime contenido de la memoria y, con %n, puede provocar una violación de segmento o corromper variables. El defecto es dependiente del dato de entrada, por lo que puede no aparecer en las pruebas felices.

Fundamento en el estándar C11

ISO/IEC 9899:2011 §7.21.6.1 describe printf: el argumento format es una cadena de formato cuyos especificadores determinan cómo se interpretan los argumentos restantes. Pasar una cadena arbitraria como formato le otorga al contenido el control de esa interpretación. El uso correcto es una máscara literal constante, y %s para insertar el texto como dato.

Corrección idiomática

❌ Código con el antipatrón

#include <stdio.h>

void mostrar_aviso(const char *texto)
{
    printf(texto);
}

Por qué es incorrecto: texto se interpreta como formato. Un mensaje con %s o %n altera el comportamiento de printf, leyendo o escribiendo memoria fuera del propósito de la función.

✅ Código refactorizado

#include <stdio.h>

void mostrar_aviso(const char *texto)
{
    printf("%s", texto);
}

La máscara es literal y constante, y texto se inserta como dato. Si solo se quiere volcar el texto, fputs(texto, stdout) es igual de claro y evita el formato por completo.

Errores típicos al compilar o ejecutar

Con las advertencias de formato activadas, el compilador detecta el patrón.

warning: format not a string literal and no format arguments
         [-Wformat-security]

En ejecución, un formato manipulado puede tumbar el proceso.

$ ./avisos '%n'
Segmentation fault (core dumped)

Un % suelto produce salida corrupta sin error de compilación.

$ ./avisos 'avance: 50%'
avance: 50% <basura leida de la pila>

Checklist de verificación

Reglas relacionadas