printf es la primera función que aprende todo programador de C y aquella cuyo manual la mayoría nunca lee del todo. Vale diez minutos, porque su cadena de formato es un pequeño lenguaje propio, y porque equivocarse en un especificador no es una errata: es comportamiento indefinido.
Vive en <stdio.h>, así que todo programa que la usa empieza con #include <stdio.h>.
El modelo de la cadena de formato
printf recorre la cadena de formato un carácter a la vez. Todo lo que no es un % se imprime exactamente como está escrito. Un % inicia una especificación de conversión, que se come el siguiente argumento y lo imprime en la forma pedida.
Los argumentos se emparejan con los especificadores estrictamente por posición: el primer % toma count, el segundo toma name, y así. printf no tiene forma de comprobar que pasaste lo que prometiste —no puede ver los tipos— así que la cadena de formato es un contrato que debes cumplir. Eso es lo que hace que valga la pena activar -Wall: GCC lee la cadena de formato literal en tiempo de compilación y avisa cuando un argumento no cuadra.
Nada obliga a un salto de línea. printf("Hola") deja el cursor después de la a, que es por lo que casi toda cadena de formato termina con \n.
Los especificadores que usas a diario
Cinco cubren casi todo el código:
| Especificador | Imprime | Llamada de ejemplo | Salida |
|---|---|---|---|
%d | un int con signo | printf("%d", -42) | -42 |
%f | un double | printf("%f", 2.5) | 2.500000 |
%s | una cadena (char *) | printf("%s", "hi") | hi |
%c | un solo carácter | printf("%c", 'A') | A |
%p | la dirección de un puntero | printf("%p", (void*)&x) | 0x7ffd... |
Dos reglas se esconden en ese fragmento. Un % literal se escribe %%; un % solitario al final de una cadena de formato es comportamiento indefinido. Y %p exige un void *, así que convierte el puntero; pasar un int * directamente es técnicamente indefinido aunque en la práctica imprima bien.
La tabla completa —cada especificador, cada modificador de longitud, la columna de scanf— está en la página de especificadores de formato. Esta página se queda con los que usas a mano.
Ancho, precisión y alineación
Entre el % y la letra puedes poner banderas, un ancho y una precisión. Así es como conviertes un bucle en una tabla.
El ancho es un mínimo, nunca un máximo: %3d con el valor 123456 imprime los seis dígitos. La precisión significa cosas distintas según el tipo: dígitos tras el punto decimal para %f, máximo de caracteres para %s, mínimo de dígitos para %d.
Ambos pueden suministrarse en tiempo de ejecución con un *, que lee un argumento int:
Cambiar width recompone la tabla entera. Esta es la forma estándar de imprimir columnas alineadas en C, sin necesidad de ninguna función auxiliar de relleno.
Imprimir decimales con sensatez
%f sin precisión siempre imprime seis dígitos tras el punto, que rara vez es lo que alguien quiere:
Tres opciones, y una regla práctica para cada una:
%.Nfpara dinero, medidas y cualquier cosa con una cantidad natural de decimales.%.2fes lo que quieres para una moneda.%epara notación científica, siempred.dddddde±dd.%gcuando no conoces la magnitud de antemano. Elige%fo%e, el que sea más corto, y quita los ceros finales: el valor por defecto más sensato para registrar un valor que no has visto.
Una trampa que conviene decir claramente: no existe %lf en printf. Los argumentos float se promocionan a double antes de que printf los vea, así que %f maneja ambos, y %lf solo tiene sentido en scanf, donde importa el tamaño del destino. (C99 sí acepta %lf en printf como sinónimo, pero el hábito de escribirlo lleva a escribir %f en scanf, que sí rompe las cosas.)
Enteros de otros anchos
%d es para int. Los tipos más grandes o sin signo necesitan un modificador de longitud, y equivocarse es comportamiento indefinido, no un error de redondeo:
%zu para size_t es el que más se equivoca la gente: sizeof devuelve size_t, no int, así que printf("%d", sizeof x) es un error que da la casualidad de que funciona en sistemas de 32 bits y falla en los de 64.
El valor de retorno
printf devuelve la cantidad de caracteres que escribió, o un número negativo si falla. Normalmente se ignora, y en general está bien, pero es genuinamente útil cuando necesitas saber cuán ancha fue la salida:
Las emparentadas sprintf y snprintf escriben en un búfer en lugar de en la pantalla, y devuelven la longitud que produjeron. Prefiere siempre snprintf, que toma el tamaño del búfer y no puede desbordarse:
snprintf devuelve la longitud que la cadena completa habría necesitado, así que un valor de retorno igual o mayor que el tamaño del búfer te dice que la salida se truncó.
La vulnerabilidad de cadena de formato
Este es el único problema de seguridad de printf, y es grave. Nunca pases como cadena de formato datos que no escribiste tú:
/* PELIGROSO: nunca hagas esto */
printf(user_input);
/* Correcto */
printf("%s", user_input);
Si user_input contiene %s, printf lee un argumento que nunca se pasó y sigue cualquier basura con forma de puntero que encuentre, normalmente reventando. Un %x repetido vuelca el contenido de la pila a la pantalla, lo que puede incluir contraseñas o claves. Y %n, que escribe el conteo de caracteres en un argumento, históricamente convirtió esto en corrupción arbitraria de memoria y ejecución remota de código.
La regla es absoluta y no cuesta nada: la cadena de formato es siempre un literal que escribiste tú. Todo lo variable entra como argumento. -Wformat-security de GCC señala el error, y está incluida en -Wall -Wextra.
Búfer: por qué a veces la salida aparece tarde
printf no escribe en la terminal de inmediato. La salida se almacena en un búfer, que se vacía cuando se llena, cuando aparece un salto de línea en una terminal o cuando el programa termina normalmente. Dos consecuencias:
- Un programa que revienta puede perder salida que "ya se imprimió". Si estás depurando con
printfy el último mensaje nunca aparece, el fallo ocurrió después de esa línea, no antes. Agregafflush(stdout)o manda la salida de depuración astderr, que no usa búfer. fprintf(stderr, ...)es la forma correcta de imprimir errores. No usa búfer, y va a un flujo separado que los usuarios pueden redirigir de forma independiente de la salida normal.
Preguntas frecuentes
¿Cómo funciona printf en C?
printf toma una cadena de formato y la recorre carácter por carácter. Los caracteres corrientes se imprimen tal cual; cada % inicia una especificación de conversión que consume uno de los argumentos siguientes y lo imprime en la forma pedida. printf("%d manzanas\n", 5) imprime 5 manzanas y un salto de línea.
¿Cómo imprimo un decimal con 2 cifras en C?
Usa una precisión: printf("%.2f\n", 3.14159) imprime 3.14. El número después del punto es cuántos dígitos siguen al punto decimal. %f sin precisión siempre imprime 6 dígitos, que es por lo que los decimales sin formatear se ven tan ruidosos.
¿Por qué printf imprime basura o revienta?
Casi siempre por un desajuste entre el especificador y el argumento. printf("%d", 3.5) o printf("%s", 42) es comportamiento indefinido: printf no puede ver los tipos reales, se fía de la cadena de formato. Compila con -Wall, que hace que GCC contraste el formato con los argumentos.
¿Por qué printf(entrada_del_usuario) es peligroso?
Si el texto del usuario contiene secuencias %, printf las obedecerá y leerá argumentos que nunca se pasaron, filtrando memoria de la pila y, con %n, incluso escribiendo en memoria. Esta es la clásica vulnerabilidad de cadena de formato. Escribe siempre printf("%s", entrada_del_usuario) en su lugar.