O scanf é o espelho do printf: mesmo cabeçalho, strings de formato parecidas, direção oposta. Ele lê texto da entrada padrão, converte conforme o formato e guarda os resultados através dos ponteiros que você fornece.
É também a função que produz mais iniciantes confusos do que qualquer outra em C, porque três dos seus comportamentos são surpreendentes e um deles é uma falha de segurança. Esta página cobre os quatro.
A forma básica, e por que o &
Digite um número e aperte Enter para rodar isso. O &age não é enfeite. O C passa todo argumento por valor, então se você escrevesse scanf("%d", age) a função receberia uma cópia do valor atual (lixo) de age e não teria como alcançar a variável em si. &age passa o endereço da variável, no qual o scanf então escreve. Veja ponteiros para o quadro completo.
Esquecer o & é o erro mais comum do scanf. O compilador com -Wall o pega (format '%d' expects argument of type 'int *'); sem avisos habilitados ele compila e corrompe a memória num endereço aleatório.
Lendo vários valores
Uma chamada pode ler vários valores. Os espaços em branco na entrada - espaços, tabulações, quebras de linha - os separam, e os especificadores numéricos pulam qualquer quantidade deles.
Os espaços literais em "%d %d %d" na verdade são redundantes - o %d já pula os espaços iniciais - mas deixam o formato legível. O que não é redundante é conferir o valor de retorno, tratado logo abaixo.
Caracteres não brancos no formato precisam casar exatamente com a entrada, e é assim que se lê entrada estruturada:
O : no formato exige um : na entrada. Digite 9:30 e ele interpreta; digite 9 30 e ele falha depois de ler o 9.
Sempre confira o valor de retorno
O scanf devolve o número de itens atribuídos com sucesso - não o número de caracteres, nem um sinalizador de sucesso. Ele devolve EOF se a entrada acabou antes de qualquer conversão.
Digite ola no prompt. O scanf devolve 0, n fica intocado - ainda com o lixo com que começou - e o texto ofensivo continua parado no fluxo de entrada. Essa última parte é o que transforma uma única entrada ruim num laço infinito:
/* BUG: gira para sempre com entrada nao numerica */
while (scanf("%d", &n) != 1) {
printf("Tente de novo: ");
}
O scanf nunca consome o texto que não conseguiu converter, então a próxima chamada falha nos mesmos caracteres, para sempre. Para se recuperar você precisa descartar o resto da linha você mesmo:
A verificação de EOF importa: quando o fluxo de entrada acaba (o usuário pressiona Ctrl+D, ou um arquivo canalizado termina), o scanf passa a devolver EOF para sempre, então um laço que só testa != 1 tenta de novo eternamente. Trate "entrada inválida" e "não há mais entrada" como os dois casos distintos que são. Com isso no lugar, vale a pena manter esse auxiliar discard_line por perto em qualquer programa que use scanf com entrada humana.
O estouro de buffer do %s
O %s lê caracteres para um array de char. Note que não há & - o nome de um array já decai para um ponteiro ao seu primeiro elemento, então &nome seria o tipo errado.
O problema é que o %s puro não tem ideia do tamanho do seu array:
char name[10];
scanf("%s", name); /* PERIGOSO: digite 40 caracteres e ele escreve 41 bytes */
Nada interrompe a escrita no fim do array. Ela avança sobre o que estiver adiante na memória - outras variáveis, o endereço de retorno - que é o clássico estouro de buffer, e historicamente a classe de bug mais explorada em C. A solução é uma largura entre o % e o s, uma unidade menor que o tamanho do array para deixar espaço para o '\0' terminador:
A largura precisa ser escrita como literal na string de formato, o que torna chato mantê-la em sincronia com o tamanho do array - mais uma razão pela qual a maior parte do C de produção usa fgets.
A segunda surpresa do %s: ele para no primeiro espaço em branco. Digite Ada Lovelace e name guarda Ada; Lovelace fica esperando no fluxo pela próxima leitura. O %s lê uma palavra, não uma linha.
O bug da quebra de linha sobrando
Esse é o "scanf não funciona" com que todo mundo esbarra. Especificadores numéricos pulam os espaços em branco iniciais; o %c não.
Eis o que acontece sem esse espaço. Você digita 30 e aperta Enter. O %d consome o 3 e o 0 e para na quebra de linha, deixando '\n' no buffer. O scanf("%c", &initial) seguinte lê essa quebra de linha como o caractere e retorna na hora - o prompt aparece e é pulado no mesmo instante.
Um espaço na string de formato do scanf significa "pule qualquer quantidade de espaço em branco aqui", então " %c" passa por cima da quebra de linha perdida e espera um caractere de verdade. O espaço só é necessário antes de %c e %[; todos os outros especificadores pulam espaços por conta própria.
Lendo floats e chars
Os especificadores são em boa parte compartilhados com o printf, com uma diferença importante: o scanf precisa saber o tamanho exato do destino, porque está escrevendo através de um ponteiro.
Teste com 1.5 2.25 7 x.
%f lê um float, %lf lê um double. No printf os dois imprimem do mesmo jeito porque floats são promovidos a double antes da chamada - mas no scanf não há promoção, e usar %f com um double * escreve quatro bytes numa variável de oito bytes, deixando-a corrompida. O %c aqui não precisa de espaço na frente porque o %d anterior já parou num espaço, não numa quebra de linha. (Tabela completa em especificadores de formato.)
fgets: a alternativa robusta
Para qualquer coisa que uma pessoa digite, leia a linha inteira e a analise depois. O fgets recebe o tamanho do buffer como argumento, então fisicamente não pode estourar, e quando a linha cabe ele consome também a quebra de linha, de modo que nada fica para trás para confundir a próxima leitura. (Uma linha maior que o buffer deixa sua cauda por ler - a falta de '\n' no que você recebeu é como você detecta isso.)
Três pontos sobre esse padrão:
sizeof linepassa o tamanho real, então mudar o tamanho do array não exige nenhuma outra edição - diferente do literal%19s.- O
fgetsmantém a quebra de linha se a linha coube, e é por isso que o corte comstrcspnestá ali. É assim também que você detecta uma linha grande demais para o buffer: não há'\n'no que você recebeu. - Analise depois.
strtolpara inteiros (ele também informa onde a análise parou),strtodpara ponto flutuante, ousscanf(line, "%d %d", &a, &b)para usar um formato de scanf contra uma string que você já tem em mãos com segurança.
O sscanf merece menção à parte: é o scanf lendo de uma string em vez do fluxo de entrada, e combina perfeitamente com o fgets. Você fica com a análise conveniente do scanf sem nenhum dos problemas de fluxo dele.
Erros comuns
- Esquecer o
&num argumento que não é array. Compile com-Wall. %ssem largura. Um estouro de buffer esperando por uma entrada longa.- Ignorar o valor de retorno. E depois ler uma variável que o
scanfnunca atribuiu. - Fazer laço sobre um
scanfque falhou sem descartar a entrada. Um laço infinito. %csem espaço na frente depois de ler um número. O bug do prompt pulado.- Usar
%fpara umdouble. Corrupção silenciosa; oscanfprecisa de%lf. - Esperar que o
%sleia uma linha. Ele lê uma palavra delimitada por espaços.
Perguntas frequentes
Por que o scanf precisa de um & em C?
Porque o C passa argumentos por valor. O scanf precisa alterar a sua variável, então ele precisa do endereço dela, não de uma cópia do valor: scanf("%d", &idade). A única exceção é um array de caracteres, cujo nome já é um endereço - scanf("%19s", nome) não leva &.
Por que o scanf pula minha entrada de char ou de string?
Um scanf("%d", ...) anterior deixou a quebra de linha da tecla Enter parada no buffer de entrada. %d e %f pulam os espaços em branco iniciais, mas %c não, então ele lê essa quebra de linha em vez de esperar. A solução é um espaço no formato - scanf(" %c", &ch) - que manda o scanf pular os espaços primeiro.
Como ler uma string com scanf de forma segura em C?
Dê ao %s uma largura máxima uma unidade menor que o seu buffer: char nome[20]; scanf("%19s", nome);. Sem a largura, o %s escreve quantos caracteres o usuário digitar e passa alegremente do fim do array. Lembre-se também de que o %s para no primeiro espaço em branco, então ele não consegue ler um nome completo com espaço.
Devo usar scanf ou fgets em C?
Use fgets para qualquer coisa que uma pessoa digite. Ele lê uma linha inteira para um buffer de tamanho conhecido, não pode estourar e, quando a linha cabe, consome também a quebra de linha, então os clássicos bugs de entrada misturada desaparecem. Analise a linha depois com sscanf ou strtol. O scanf serve bem para exercícios pequenos e para entrada bem formada vinda de máquinas.