Menu

printf em C: strings de formato, especificadores e como imprimir floats

Como o printf funciona de verdade em C - o modelo da string de formato, os especificadores do dia a dia, largura e precisão para saída alinhada, como imprimir floats com bom senso, o valor de retorno e por que printf(entrada_do_usuario) é uma falha de segurança.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

printf é a primeira função que todo programador C aprende e aquela cujo manual a maioria nunca leu até o fim. Vale dez minutos, porque a string de formato dele é uma pequena linguagem à parte - e porque errar um especificador não é um erro de digitação, é comportamento indefinido.

Ele mora em <stdio.h>, então todo programa que o usa começa com #include <stdio.h>.

O modelo da string de formato

O printf percorre a string de formato um caractere por vez. Tudo que não é % é impresso exatamente como escrito. Um % inicia uma especificação de conversão, que consome o próximo argumento e o imprime no formato pedido.

Os argumentos são casados com os especificadores estritamente por posição: o primeiro % pega count, o segundo pega name, e assim por diante. O printf não tem como conferir se você passou o que prometeu - ele não enxerga os tipos - então a string de formato é um contrato que cabe a você cumprir. É isso que torna o -Wall digno de ser ligado: o GCC lê a string de formato literal em tempo de compilação e avisa quando um argumento não bate.

Nada força uma quebra de linha. printf("Ola") deixa o cursor depois do a, e é por isso que quase toda string de formato termina com \n.

Os especificadores do dia a dia

Cinco deles cobrem a maior parte do código:

EspecificadorImprimeChamada de exemploSaída
%dum int com sinalprintf("%d", -42)-42
%fum doubleprintf("%f", 2.5)2.500000
%suma string (char *)printf("%s", "oi")oi
%cum único caractereprintf("%c", 'A')A
%po endereço de um ponteiroprintf("%p", (void*)&x)0x7ffd...

Duas regras se escondem nesse trecho. Um % literal se escreve %% - um % sozinho no fim de uma string de formato é comportamento indefinido. E %p exige um void *, então converta o ponteiro; passar um int * direto é tecnicamente indefinido, mesmo que na prática imprima bem.

A tabela completa - todos os especificadores, todos os modificadores de comprimento, a coluna do scanf - está na página de especificadores de formato. Esta página fica nos que você usa toda hora.

Largura, precisão e alinhamento

Entre o % e a letra você pode colocar flags, uma largura e uma precisão. É assim que um laço vira uma tabela.

A largura é um mínimo, nunca um máximo: %3d com o valor 123456 imprime os seis dígitos. A precisão significa coisas diferentes por tipo - dígitos depois do ponto decimal para %f, máximo de caracteres para %s, mínimo de dígitos para %d.

As duas podem ser fornecidas em tempo de execução com um *, que lê um argumento int:

Mudar width reorganiza a tabela inteira. Essa é a maneira padrão de imprimir colunas alinhadas em C - sem precisar de nenhuma função auxiliar de preenchimento de string.

Imprimindo floats com bom senso

%f sem precisão sempre imprime seis dígitos depois do ponto, o que raramente é o que alguém quer:

Três escolhas, e uma regra prática para cada uma:

  • %.Nf para dinheiro, medidas, qualquer coisa com um número natural de casas decimais. %.2f é o que você quer para moeda.
  • %e para notação científica, sempre d.dddddde±dd.
  • %g quando você não conhece a ordem de grandeza de antemão. Ele escolhe %f ou %e, o que for mais curto, e tira os zeros à direita - o padrão mais sensato para registrar um valor que você ainda não viu.

Uma armadilha que vale dizer com todas as letras: não existe %lf no printf. Argumentos float são promovidos a double antes de o printf sequer vê-los, então %f dá conta dos dois, e %lf só faz sentido no scanf, onde o tamanho do destino importa. (O C99 até aceita %lf no printf como sinônimo, mas o hábito de escrevê-lo leva as pessoas a escrever %f no scanf, o que aí sim quebra de verdade.)

Inteiros de outros tamanhos

%d é para int. Tipos maiores ou sem sinal precisam de um modificador de comprimento, e errar isso é comportamento indefinido, não um erro de arredondamento:

%zu para size_t é o que as pessoas mais erram - sizeof devolve size_t, não int, então printf("%d", sizeof x) é um bug que por acaso funciona em sistemas de 32 bits e se comporta mal nos de 64.

O valor de retorno

O printf devolve o número de caracteres que escreveu, ou um número negativo em caso de falha. Ele costuma ser ignorado, e na maior parte do tempo tudo bem - mas é genuinamente útil quando você precisa saber qual foi a largura da saída:

As parentes sprintf e snprintf escrevem num buffer em vez de na tela, e devolvem o comprimento que produziram. Prefira sempre o snprintf, que recebe o tamanho do buffer e não pode estourá-lo:

O snprintf devolve o comprimento que a string completa teria precisado, então um valor de retorno igual ou maior que o tamanho do buffer avisa que a saída foi truncada.

A vulnerabilidade de format string

Esse é o único problema de segurança do printf, e ele é grave. Nunca passe como string de formato dados que você não escreveu:

/* PERIGOSO - nunca faca isso */
printf(entrada_do_usuario);

/* Correto */
printf("%s", entrada_do_usuario);

Se entrada_do_usuario contiver %s, o printf lê um argumento que nunca foi passado e segue qualquer lixo em forma de ponteiro que encontrar, geralmente travando. %x repetido despeja o conteúdo da pilha na tela, o que pode incluir senhas ou chaves. E %n, que escreve a contagem de caracteres num argumento, historicamente transformou isso em corrupção arbitrária de memória e execução remota de código.

A regra é absoluta e não custa nada: a string de formato é sempre um literal escrito por você. Tudo que for variável entra como argumento. O -Wformat-security do GCC sinaliza o erro, e ele já vem incluído em -Wall -Wextra.

Bufferização: por que a saída às vezes aparece atrasada

O printf não escreve no terminal imediatamente. A saída é bufferizada - descarregada quando o buffer enche, quando aparece uma quebra de linha num terminal, ou quando o programa termina normalmente. Duas consequências:

  • Um programa que trava pode perder saída que "já foi impressa". Se você está depurando com printf e a última mensagem nunca aparece, o travamento aconteceu depois daquela linha, não antes. Acrescente fflush(stdout) ou mande a saída de depuração para stderr, que não é bufferizada.
  • fprintf(stderr, ...) é a forma certa de imprimir erros. Ela não é bufferizada e vai para um fluxo separado que os usuários podem redirecionar independentemente da saída normal.

Perguntas frequentes

Como o printf funciona em C?

O printf recebe uma string de formato e a percorre caractere por caractere. Os caracteres comuns são impressos como estão; cada % inicia uma especificação de conversão que consome um dos argumentos seguintes e o imprime no formato pedido. printf("%d macas\n", 5) imprime 5 macas e uma quebra de linha.

Como imprimir um float com 2 casas decimais em C?

Use uma precisão: printf("%.2f\n", 3.14159) imprime 3.14. O número depois do ponto é quantos dígitos vêm após a vírgula decimal. %f sem precisão sempre imprime 6 dígitos, e é por isso que floats sem formatação ficam tão poluídos.

Por que o printf imprime lixo ou trava?

Quase sempre por uma incompatibilidade entre o especificador e o argumento. printf("%d", 3.5) ou printf("%s", 42) é comportamento indefinido - o printf não enxerga os tipos reais, ele confia na string de formato. Compile com -Wall, o que faz o GCC conferir o formato contra os argumentos.

Por que printf(entrada_do_usuario) é perigoso?

Se o texto do usuário contiver sequências %, o printf vai obedecê-las e ler argumentos que nunca foram passados - vazando memória da pilha e, com %n, até escrevendo na memória. Essa é a clássica vulnerabilidade de format string. Escreva sempre printf("%s", entrada_do_usuario) no lugar.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR