Un especificador de formato le dice a printf cómo convertir un valor en texto, y a scanf cómo convertir texto en un valor. Es la única parte de C donde el compilador no puede ayudarte por defecto —los tipos de los argumentos son invisibles para la función— así que esta página es la tabla con la que contrastar.
La forma general de una especificación es:
%[banderas][ancho][.precision][longitud]conversion
Solo el % y la letra de conversión son obligatorios. %-8.2lf usa las cinco partes.
La tabla de conversiones
| Especificador | Significado | Argumento de printf | Argumento de scanf |
|---|---|---|---|
%d | entero decimal con signo | int | int * |
%i | entero con signo | int (igual que %d) | int * (también acepta 0x, octal con 0) |
%u | decimal sin signo | unsigned int | unsigned int * |
%f | punto flotante decimal | double | float * |
%F | como %f, con INF/NAN en mayúsculas | double | - |
%e / %E | notación científica | double | float * |
%g / %G | el más corto entre %f y %e | double | float * |
%a / %A | punto flotante hexadecimal | double | float * |
%c | un solo carácter | int (char promocionado) | char * |
%s | cadena | char * (terminada en '\0') | char * (búfer) |
%p | dirección de un puntero | void * | void ** |
%x / %X | hexadecimal sin signo | unsigned int | unsigned int * |
%o | octal sin signo | unsigned int | unsigned int * |
%n | guarda los caracteres escritos hasta ahí | int * | int * |
%% | un % literal | ninguno | coincide con un % literal |
%[...] | conjunto de caracteres (solo scanf) | - | char * |
Dos entradas merecen una advertencia. %n escribe en memoria en lugar de imprimir, y es el mecanismo detrás de la vulnerabilidad de cadena de formato: nunca dejes que texto del usuario llegue a una cadena de formato. Y %p exige un void *; pasar un int * directamente es indefinido aunque todos los compiladores reales impriman la dirección igual.
Modificadores de longitud
El modificador de longitud va entre la precisión y la letra de conversión, y dice qué tan grande es realmente el argumento.
Las conversiones con signo (d i) y las sin signo (u x o) toman tipos distintos: %lu quiere un unsigned long, no un long, y equivocarse ahí es comportamiento indefinido, no un desliz cosmético.
| Modificador | Con d i (printf / scanf) | Con u x o (printf / scanf) |
|---|---|---|
hh | int (desde signed char) / signed char * | unsigned int (desde unsigned char) / unsigned char * |
h | int (desde short) / short * | unsigned int (desde unsigned short) / unsigned short * |
| (ninguno) | int / int * | unsigned int / unsigned int * |
l | long / long * | unsigned long / unsigned long * |
ll | long long / long long * | unsigned long long / unsigned long long * |
z | tipo de tamaño con signo / puntero a él | size_t / size_t * |
j | intmax_t / intmax_t * | uintmax_t / uintmax_t * |
t | ptrdiff_t / ptrdiff_t * | contraparte sin signo de ptrdiff_t |
Y para las familias de punto flotante y de caracteres:
| Modificador | Con | Tipo en printf | Tipo en scanf |
|---|---|---|---|
| (ninguno) | f e g a | double | float * |
l | f e g a | double (aceptado, sin efecto) | double * |
L | f e g a | long double | long double * |
l | c s | wint_t / wchar_t * | wchar_t * |
Las combinaciones que de verdad escribes:
%zu es el que hay que memorizar. sizeof, strlen y cualquier tamaño de la biblioteca estándar devuelven size_t, que ocupa 8 bytes en un sistema de 64 bits mientras que int ocupa 4. printf("%d", strlen(s)) es un error real que parece funcionar en algunas plataformas: usa %zu.
Los modificadores hh y h existen sobre todo para scanf, donde importa el tamaño del destino. En printf un short se promociona a int antes de la llamada, así que %d lo imprimiría bien de todos modos; %hd solo documenta la intención.
Tipos de ancho fijo
Para los tipos de <stdint.h> como int32_t, los especificadores portables son macros de <inttypes.h>:
PRId32 se expande a las letras correctas para tu plataforma, y los literales de cadena adyacentes se concatenan, que es por lo que el formato queda partido en tres piezas. Es feo; la alternativa —convertir a long long y usar %lld— suele ser más legible y siempre es correcta.
Banderas
Las banderas van inmediatamente después del % y cambian la presentación. Se aplican solo a printf.
| Bandera | Efecto | Ejemplo | Salida |
|---|---|---|---|
- | rellena después del valor en vez de antes | %-6d| con 42 | 42 | |
+ | imprime siempre el signo en valores con signo | %+d con 42 | +42 |
| espacio | imprime un espacio donde iría un + | % d con 42 | 42 |
0 | rellena con ceros en lugar de espacios | %06d con 42 | 000042 |
# | forma alternativa: 0x para %x, 0 para %o, conserva el punto en %g | %#x con 255 | 0xff |
Las banderas se combinan: %-+8.2f es ancho mínimo 8, dos decimales, siempre con signo, rellenado después del número. - gana sobre 0 si escribes ambas.
Ancho y precisión
El ancho es un tamaño mínimo de campo: la salida se rellena hasta alcanzarlo, y nunca se trunca para caber en él. La precisión (después de un punto) significa algo distinto para cada familia:
| Conversión | La precisión significa |
|---|---|
%f %e | dígitos después del punto decimal (6 por defecto) |
%g | dígitos significativos totales (6 por defecto) |
%s | máximo de caracteres a imprimir |
%d %i %u %x %o | mínimo de dígitos, rellenado con ceros |
Un * en lugar de cualquiera de los dos números lo toma de un argumento int, leído antes del valor en sí. Así se construyen tablas cuyos anchos de columna se calculan en tiempo de ejecución.
En scanf el ancho tiene un trabajo distinto y mucho más importante: limita cuánta entrada consume una conversión, que es lo único que se interpone entre %s y un desbordamiento de búfer.
Dónde difieren printf y scanf
Se parecen y no coinciden en cuatro puntos. Cada uno de ellos es una fuente viva de errores:
| Situación | printf | scanf |
|---|---|---|
Valor double | %f (float promocionado a double) | %lf: %f significa float * |
| Ancho | tamaño mínimo de campo, rellena | máximo de entrada consumida, trunca |
| Argumentos | valores | punteros: &x, o el nombre de un array |
| Un espacio en el formato | imprime un espacio | salta cualquier racha de espacios en blanco |
Prueba con 2.5 hola. El formato que lo lee y el que lo imprime usan letras distintas para el mismo double, y el 15 de %15s limita la entrada a 15 caracteres más el terminador, exactamente el tamaño de word.
El conjunto de caracteres de scanf
%[...] lee un conjunto de caracteres en vez de una palabra delimitada por espacios. Es la forma de leer una línea completa con scanf, aunque fgets sigue siendo la mejor herramienta:
[^\n] significa "cualquier carácter excepto un salto de línea". %[0-9] leería solo dígitos, %[abc] solo esas tres letras. A diferencia de %s, un conjunto de caracteres no salta los espacios iniciales, así que un salto de línea suelto en el búfer hace que no coincida con nada.
Equivocarse es comportamiento indefinido
printf no puede ver el tipo real de tu argumento. Lee la cantidad de bytes que implica el especificador, interpretados como el especificador diga. Así que un desajuste no es un fallo de formato: es leer memoria como lo que no es.
printf("%d\n", 3.5); /* imprime basura: lee los bits de un double como int */
printf("%s\n", 42); /* trata 42 como una direccion: revienta */
printf("%d\n", strlen(s)); /* size_t leido como int: mal en 64 bits */
printf("%f\n", 3); /* int leido como double: basura */
La defensa cuesta una bandera. Compila con -Wall y GCC contrasta cada cadena de formato literal con sus argumentos:
gcc -Wall -Wextra program.c -o program
Reporta cada una de las cuatro líneas de arriba en tiempo de compilación. Es el aviso de mayor valor de todo C, y está activado por defecto en el editor de arriba.
Para el lado práctico de usarlos —tablas alineadas, imprimir decimales de forma legible, leer entrada sin el clásico error del salto de línea— mira printf y scanf.
Preguntas frecuentes
¿Qué significa %d en C?
%d es el especificador de conversión para un entero decimal con signo, es decir un int. En printf imprime el argumento como dígitos en base 10; en scanf lee dígitos en base 10 hacia un int *. %i significa lo mismo en printf, pero en scanf %i además acepta hexadecimal con 0x y octal con cero inicial.
¿Cuál es la diferencia entre %f y %lf en C?
En printf no hay diferencia práctica: los argumentos float se promocionan a double, así que %f imprime ambos, y C99 acepta %lf como sinónimo. En scanf la diferencia es crítica: %f escribe en un float * (4 bytes) y %lf en un double * (8 bytes). Usar %f para un double corrompe memoria.
¿Cómo imprimo un size_t o un long long en C?
%zu para size_t (lo que devuelven sizeof y strlen) y %lld / %llu para long long / unsigned long long. Usar %d para estos es comportamiento indefinido: da la casualidad de que funciona en sistemas de 32 bits y falla en los de 64, que es por lo que el error sobrevive tanto tiempo.
¿Qué pasa si usas el especificador de formato equivocado en C?
Es comportamiento indefinido, no un error de redondeo. printf lee el argumento como lo que el especificador afirme, así que printf("%d", 3.5) imprime basura y printf("%s", 42) sigue el número 42 como si fuera una dirección y normalmente revienta. Compila con -Wall, que hace que GCC contraste las cadenas de formato con los argumentos.