Um especificador de formato diz ao printf como transformar um valor em texto, e ao scanf como transformar texto em um valor. É a única parte do C em que o compilador não consegue te ajudar por padrão - os tipos dos argumentos são invisíveis para a função - então esta página é a tabela de consulta.
A forma geral de uma especificação é:
%[flags][largura][.precisao][tamanho]conversao
Só o % e a letra de conversão são obrigatórios. %-8.2lf usa as cinco partes.
A tabela de conversões
| Especificador | Significado | Argumento no printf | Argumento no scanf |
|---|---|---|---|
%d | inteiro decimal com sinal | int | int * |
%i | inteiro com sinal | int (igual a %d) | int * (aceita também 0x, octal com 0) |
%u | decimal sem sinal | unsigned int | unsigned int * |
%f | ponto flutuante decimal | double | float * |
%F | como %f, com INF/NAN em maiúsculas | double | - |
%e / %E | notação científica | double | float * |
%g / %G | o mais curto entre %f / %e | double | float * |
%a / %A | ponto flutuante hexadecimal | double | float * |
%c | um único caractere | int (char promovido) | char * |
%s | string | char * (terminada em '\0') | char * (buffer) |
%p | endereço de ponteiro | void * | void ** |
%x / %X | hexadecimal sem sinal | unsigned int | unsigned int * |
%o | octal sem sinal | unsigned int | unsigned int * |
%n | guarda quantos caracteres já foram escritos | int * | int * |
%% | um % literal | nenhum | casa com um % literal |
%[...] | scanset (só no scanf) | - | char * |
Duas entradas merecem um aviso. %n escreve na memória em vez de imprimir, e é o mecanismo por trás da vulnerabilidade de string de formato - nunca deixe texto do usuário chegar a uma string de formato. E %p exige um void *; passar um int * diretamente é indefinido, mesmo que todo compilador real imprima o endereço mesmo assim.
Modificadores de tamanho
O modificador de tamanho fica entre a precisão e a letra de conversão, e diz qual é o tamanho real do argumento.
As conversões com sinal (d i) e as sem sinal (u x o) recebem tipos diferentes - %lu quer um unsigned long, não um long, e errar isso é comportamento indefinido, não um deslize cosmético.
| Modificador | Com d i (printf / scanf) | Com u x o (printf / scanf) |
|---|---|---|
hh | int (vindo de signed char) / signed char * | unsigned int (vindo de unsigned char) / unsigned char * |
h | int (vindo de short) / short * | unsigned int (vindo de unsigned short) / unsigned short * |
| (nenhum) | 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 tamanho com sinal / ponteiro para ele | size_t / size_t * |
j | intmax_t / intmax_t * | uintmax_t / uintmax_t * |
t | ptrdiff_t / ptrdiff_t * | contraparte sem sinal de ptrdiff_t |
E para as famílias de ponto flutuante e de caracteres:
| Modificador | Com | Tipo no printf | Tipo no scanf |
|---|---|---|---|
| (nenhum) | f e g a | double | float * |
l | f e g a | double (aceito, sem efeito) | double * |
L | f e g a | long double | long double * |
l | c s | wint_t / wchar_t * | wchar_t * |
As combinações que você de fato digita:
%zu é o que você precisa memorizar. sizeof, strlen e todo tamanho da biblioteca padrão devolvem size_t, que tem 8 bytes em um sistema de 64 bits, enquanto int tem 4. printf("%d", strlen(s)) é um bug real que aparenta funcionar em algumas plataformas - use %zu.
Os modificadores hh e h existem principalmente para o scanf, onde o tamanho do destino importa. No printf um short é promovido a int antes da chamada, então %d o imprimiria corretamente de qualquer forma; %hd apenas documenta a intenção.
Tipos de largura fixa
Para tipos de <stdint.h> como int32_t, os especificadores portáveis são macros de <inttypes.h>:
PRId32 se expande nas letras certas para a sua plataforma, e literais de string adjacentes se concatenam, que é por que o formato está partido em três pedaços. É feio; a alternativa - converter para long long e usar %lld - costuma ser mais legível e é sempre correta.
Flags
As flags vêm logo depois do % e mudam a apresentação. Elas valem só para o printf.
| Flag | Efeito | Exemplo | Saída |
|---|---|---|---|
- | preenche depois do valor em vez de antes | %-6d| com 42 | 42 | |
+ | sempre imprime um sinal para valores com sinal | %+d com 42 | +42 |
| espaço | imprime um espaço onde um + iria | % d com 42 | 42 |
0 | preenche com zeros em vez de espaços | %06d com 42 | 000042 |
# | forma alternativa: 0x para %x, 0 para %o, mantém o ponto no %g | %#x com 255 | 0xff |
As flags podem se combinar: %-+8.2f é largura mínima 8, duas casas decimais, sempre com sinal, preenchido depois do número. O - vence o 0 se você escrever os dois.
Largura e precisão
Largura é um tamanho mínimo de campo - a saída é preenchida até alcançá-lo, e nunca é truncada para caber nele. Precisão (depois de um ponto) significa coisas diferentes para cada família:
| Conversão | A precisão significa |
|---|---|
%f %e | dígitos depois da vírgula decimal (padrão 6) |
%g | total de dígitos significativos (padrão 6) |
%s | máximo de caracteres a imprimir |
%d %i %u %x %o | mínimo de dígitos, preenchido com zeros |
Um * no lugar de qualquer um dos números o toma de um argumento int, lido antes do próprio valor. É assim que você monta tabelas cujas larguras de coluna são calculadas em tempo de execução.
No scanf a largura tem um papel diferente e bem mais importante: ela limita quanta entrada uma conversão vai consumir, que é a única coisa entre um %s e um estouro de buffer.
Onde printf e scanf divergem
Eles se parecem e discordam em quatro pontos. Cada um deles é uma fonte viva de bugs:
| Situação | printf | scanf |
|---|---|---|
Valor double | %f (float promovido a double) | %lf - %f significa float * |
| Largura | tamanho mínimo de campo, preenche | máximo de entrada consumida, trunca |
| Argumentos | valores | ponteiros - &x, ou o nome de um array |
| Um espaço no formato | imprime um espaço | pula qualquer sequência de espaços em branco |
Experimente 2.5 ola. O formato que lê e o formato que imprime usam letras diferentes para o mesmo double, e o 15 em %15s limita a entrada a 15 caracteres mais o terminador - exatamente o tamanho de palavra.
O scanset do scanf
%[...] lê um conjunto de caracteres em vez de uma palavra delimitada por espaços. É a forma de ler uma linha inteira com scanf, embora o fgets continue sendo a ferramenta melhor:
[^\n] significa "quaisquer caracteres exceto uma quebra de linha". %[0-9] leria só dígitos, %[abc] só essas três letras. Diferente do %s, um scanset não pula espaços em branco iniciais, então uma quebra de linha perdida no buffer faz com que ele não case com nada.
Errar é comportamento indefinido
O printf não enxerga o tipo real do seu argumento. Ele lê a quantidade de bytes que o especificador implica, interpretada como o especificador manda. Então um descasamento não é uma falha de formatação - é ler memória como a coisa errada:
printf("%d\n", 3.5); /* imprime lixo: le os bits de um double como int */
printf("%s\n", 42); /* trata 42 como um endereco - falha */
printf("%d\n", strlen(s)); /* size_t lido como int - errado em 64 bits */
printf("%f\n", 3); /* int lido como double - lixo */
A defesa custa uma flag. Compile com -Wall e o GCC confere toda string de formato literal contra seus argumentos:
gcc -Wall -Wextra program.c -o program
Ele reporta cada uma das quatro linhas acima em tempo de compilação. Este é o aviso de maior valor em C, e ele está ligado por padrão no editor acima.
Para o lado prático de usar tudo isso - tabelas alinhadas, imprimir floats de forma legível, ler entrada sem o clássico bug da quebra de linha - veja printf e scanf.
Perguntas frequentes
O que %d significa em C?
%d é o especificador de conversão para um inteiro decimal com sinal - um int. No printf ele imprime o argumento como dígitos de base 10; no scanf ele lê dígitos de base 10 para um int *. %i significa a mesma coisa no printf, mas no scanf o %i também aceita hexadecimal 0x e octal com zero à esquerda.
Qual é a diferença entre %f e %lf em C?
No printf não há diferença prática: argumentos float são promovidos a double, então %f imprime os dois, e o C99 aceita %lf como sinônimo. No scanf a diferença é crítica - %f escreve em um float * (4 bytes) e %lf em um double * (8 bytes). Usar %f para um double corrompe memória.
Como imprimir um size_t ou um long long em C?
%zu para size_t (o que sizeof e strlen devolvem) e %lld / %llu para long long / unsigned long long. Usar %d para esses é comportamento indefinido - funciona por acaso em sistemas de 32 bits e se comporta mal nos de 64, que é por que o bug sobrevive tanto tempo.
O que acontece se você usar o especificador de formato errado em C?
É comportamento indefinido, não um erro de arredondamento. O printf lê o argumento como aquilo que o especificador alega, então printf("%d", 3.5) imprime lixo e printf("%s", 42) segue o número 42 como um endereço e normalmente trava. Compile com -Wall, que faz o GCC conferir as strings de formato contra os argumentos.