Desvantagens do scanf

Eu quero saber as desvantagens do scanf() .

Em muitos sites, li que usar o scanf pode causar estouro de buffer. Qual é a razão para isto? Existem outras desvantagens com o scanf ?

    Os problemas com o scanf são (no mínimo):

    • usando %s para obter uma string do usuário, o que leva à possibilidade de que a string possa ser maior que seu buffer, causando estouro.
    • a possibilidade de uma varredura com falha deixando o ponteiro do arquivo em um local indeterminado.

    Eu prefiro muito mais usar fgets para ler linhas inteiras de modo que você possa limitar a quantidade de dados lidos. Se você tem um buffer de 1K, e você lê uma linha nele com fgets você pode dizer se a linha era muito longa pelo fato de não haver nenhum caractere de nova linha de terminação (última linha de um arquivo sem uma nova linha, não obstante).

    Então você pode reclamar com o usuário ou alocar mais espaço para o resto da linha (continuamente, se necessário, até que você tenha espaço suficiente). Em ambos os casos, não há risco de estouro de buffer.

    Depois de ler a linha, você sabe que está posicionado na próxima linha, então não há problema. Você pode então sscanf sua string para o conteúdo do seu coração sem ter que salvar e restaurar o ponteiro do arquivo para releitura.

    Aqui está um trecho de código que eu uso com frequência para garantir que não haja estouro de buffer ao solicitar informações ao usuário.

    Poderia ser facilmente ajustado para usar um arquivo diferente da input padrão, se necessário, e você também poderia alocar seu próprio buffer (e continuar aumentando até que fosse grande o suficiente) antes de devolvê-lo ao chamador (embora o chamador fosse responsável para libertá-lo, claro).

     #include  #include  #define OK 0 #define NO_INPUT 1 #define TOO_LONG 2 #define SMALL_BUFF 3 static int getLine (char *prmpt, char *buff, size_t sz) { int ch, extra; // Size zero or one cannot store enough, so don't even // try - we need space for at least newline and terminator. if (sz < 2) return SMALL_BUFF; // Output prompt. if (prmpt != NULL) { printf ("%s", prmpt); fflush (stdout); } // Get line with buffer overrun protection. if (fgets (buff, sz, stdin) == NULL) return NO_INPUT; // If it was too long, there'll be no newline. In that case, we flush // to end of line so that excess doesn't affect the next call. size_t lastPos = strlen(buff) - 1; if (buff[lastPos] != '\n') { extra = 0; while (((ch = getchar()) != '\n') && (ch != EOF)) extra = 1; return (extra == 1) ? TOO_LONG : OK; } // Otherwise remove newline and give string back to caller. buff[lastPos] = '\0'; return OK; } 

    E, um driver de teste para isso:

     // Test program for getLine(). int main (void) { int rc; char buff[10]; rc = getLine ("Enter string> ", buff, sizeof(buff)); if (rc == NO_INPUT) { // Extra NL since my system doesn't output that on EOF. printf ("\nNo input\n"); return 1; } if (rc == TOO_LONG) { printf ("Input too long [%s]\n", buff); return 1; } printf ("OK [%s]\n", buff); return 0; } 

    Finalmente, um teste para mostrá-lo em ação:

     $ ./tstprg Enter string>[CTRL-D] No input $ ./tstprg Enter string> a OK [a] $ ./tstprg Enter string> hello OK [hello] $ ./tstprg Enter string> hello there Input too long [hello the] $ ./tstprg Enter string> i am pax OK [i am pax] 

    A maioria das respostas até agora parece se concentrar no problema de estouro de buffer de seqüência de caracteres. Na realidade, os especificadores de formato que podem ser usados ​​com funções scanf suportam configuração de largura de campo explícita, o que limita o tamanho máximo da input e evita o estouro de buffer. Isso torna as acusações populares de perigos de estouro de buffer de string presentes no scanf virtualmente sem base. Afirmar que o scanf é, de algum modo, análogo ao respeito, é completamente incorreto. Há uma grande diferença qualitativa entre o scanf e o gets : o scanf fornece ao usuário resources que impedem o estouro de buffer de strings, enquanto o gets não gets isso.

    Pode-se argumentar que esses resources do scanf são difíceis de usar, já que a largura do campo deve ser incorporada na string de formato (não há como passar por um argumento variádico, como pode ser feito em printf ). Isso é realmente verdade. scanf é de fato mal projetado a esse respeito. Mas, mesmo assim, qualquer alegação de que o scanf está, de alguma forma, irremediavelmente quebrado com relação à segurança de estouro de buffer de strings é completamente falsa e geralmente feita por programadores preguiçosos.

    O problema real com o scanf tem uma natureza completamente diferente, mesmo que seja também sobre estouro . Quando a function scanf é usada para converter representações decimais de números em valores de tipos aritméticos, ela não oferece proteção contra o estouro aritmético. Se o excesso ocorrer, o scanf produzirá um comportamento indefinido. Por esse motivo, a única maneira correta de realizar a conversão na biblioteca padrão C é a function strto... family.

    Então, para resumir o acima, o problema com o scanf é que é difícil (embora possível) usar corretamente e com segurança com buffers de string. E é impossível usar com segurança para input aritmética. Este último é o problema real. O primeiro é apenas um inconveniente.

    PS O acima em destina-se a ser sobre toda a família de funções scanf (incluindo também fscanf e sscanf ). Com o scanf especificamente, a questão óbvia é que a própria idéia de usar uma function estritamente formatada para ler inputs potencialmente interativas é bastante questionável.

    Da FAQ comp.lang.c: Por que todos dizem para não usar o scanf? O que devo usar em vez disso?

    scanf tem vários problemas – veja as perguntas 12.17 , 12.18a e 12.19 . Além disso, seu formato %s tem o mesmo problema que gets() tem (veja a pergunta 12.23 ) – é difícil garantir que o buffer de recebimento não transborde. [nota de rodapé]

    Mais genericamente, o scanf é projetado para input formatada e relativamente estruturada (seu nome é, na verdade, derivado de “formatado por varredura”). Se você prestar atenção, ele lhe dirá se foi bem-sucedido ou falhou, mas pode dizer apenas onde ele falhou, e não como ou por quê. Você tem muito pouca oportunidade de fazer qualquer recuperação de erro.

    No entanto, a input interativa do usuário é a input menos estruturada que existe. Uma interface de usuário bem projetada permitirá a possibilidade do usuário digitar praticamente qualquer coisa – não apenas letras ou pontuação quando os dígitos forem esperados, mas também mais ou menos caracteres do que o esperado, ou nenhum caractere ( isto é , apenas o RETORNO). key), ou EOF prematuro, ou qualquer coisa. É quase impossível lidar com todos esses possíveis problemas ao usar o scanf ; é muito mais fácil ler linhas inteiras (com fgets ou algo parecido), então interpretá-las, usando sscanf ou algumas outras técnicas. (Funções como strtol , strtok e atoi são frequentemente úteis; veja também as questões 12.16 e 13.6 .) Se você usar qualquer variante scanf , certifique-se de verificar o valor de retorno para se certificar de que o número esperado de itens foi encontrado. Além disso, se você usar %s , proteja-se contra o estouro de buffer.

    Note, a propósito, que críticas ao scanf não são necessariamente indícios de fscanf e sscanf . scanf lê de stdin , que geralmente é um teclado interativo e, portanto, é o menos restrito, levando a mais problemas. Quando um arquivo de dados tem um formato conhecido, por outro lado, pode ser apropriado lê-lo com o fscanf . É perfeitamente apropriado analisar strings com sscanf (desde que o valor de retorno esteja marcado), porque é muito fácil recuperar o controle, reiniciar a varredura, descartar a input se ela não corresponder, etc.

    Links adicionais:

    • mais explicação por Chris Torek
    • mais explicação por sinceramente

    Referências: K & R2 Sec. 7,4 p. 159

    Sim você está certo. Existe uma grande falha de segurança na família scanf ( scanf , sscanf , fscanf ..etc) ao ler uma string, porque eles não levam em conta o comprimento do buffer (no qual estão lendo).

    Exemplo:

     char buf[3]; sscanf("abcdef","%s",buf); 

    claramente o buffer buf pode conter MAX 3 char. Mas o sscanf tentará colocar "abcdef" nele causando estouro de buffer.

    É muito difícil fazer o scanf fazer o que você quer. Claro, você pode, mas coisas como scanf("%s", buf); são tão perigosas quanto as que gets(buf); como todos disseram.

    Como exemplo, o que o paxdiablo está fazendo em sua function de ler pode ser feito com algo como:

     scanf("%10[^\n]%*[^\n]", buf)); getchar(); 

    O texto acima lerá uma linha, armazenará os 10 primeiros caracteres não pertencentes à nova linha no buf e descartará tudo até (e incluindo) uma nova linha. Então, a function do paxdiablo poderia ser escrita usando o scanf da seguinte maneira:

     #include  enum read_status { OK, NO_INPUT, TOO_LONG }; static int get_line(const char *prompt, char *buf, size_t sz) { char fmt[40]; int i; int nscanned; printf("%s", prompt); fflush(stdout); sprintf(fmt, "%%%zu[^\n]%%*[^\n]%%n", sz-1); /* read at most sz-1 characters on, discarding the rest */ i = scanf(fmt, buf, &nscanned); if (i > 0) { getchar(); if (nscanned >= sz) { return TOO_LONG; } else { return OK; } } else { return NO_INPUT; } } int main(void) { char buf[10+1]; int rc; while ((rc = get_line("Enter string> ", buf, sizeof buf)) != NO_INPUT) { if (rc == TOO_LONG) { printf("Input too long: "); } printf("->%s< -\n", buf); } return 0; } 

    Um dos outros problemas com o scanf é seu comportamento em caso de estouro. Por exemplo, ao ler um int :

     int i; scanf("%d", &i); 

    o acima não pode ser usado com segurança no caso de um estouro. Mesmo para o primeiro caso, ler uma string é muito mais simples com fgets do que com scanf .

    Problemas que tenho com a família *scanf() :

    • Potencial de estouro de buffer com% s e% [especificadores de conversão. Sim, você pode especificar uma largura máxima de campo, mas diferentemente de printf() , você não pode torná-lo um argumento na chamada scanf() ; deve ser codificado no especificador de conversão.
    • Potencial de estouro aritmético com% d,% i, etc.
    • Capacidade limitada para detectar e rejeitar inputs mal formadas. Por exemplo, “12w4” não é um inteiro válido, mas scanf("%d", &value); irá converter com sucesso e atribuir 12 ao value , deixando o “w4” preso no stream de input para interromper uma leitura futura. Idealmente, toda a string de input deve ser rejeitada, mas o scanf() não oferece um mecanismo fácil para isso.

    Se você souber que sua input sempre será bem formada com strings de tamanho fixo e valores numéricos que não flertam com overflow, então scanf() é uma ótima ferramenta. Se você está lidando com inputs ou inputs interativas que não são garantidas de serem bem formadas, use outra coisa.

    Há um grande problema com funções do tipo scanf – a falta de qualquer tipo de segurança. Isto é, você pode codificar isso:

     int i; scanf("%10s", &i); 

    Inferno, até isso é “bem”:

     scanf("%10s", i); 

    É pior do que funções do tipo printf , porque o scanf espera um ponteiro, então falhas são mais prováveis.

    Claro, existem alguns verificadores de especificação de formato, mas eles não são perfeitos e não fazem parte da linguagem ou da biblioteca padrão.

    A vantagem do scanf é uma vez que você aprende como usar a ferramenta, como você sempre deve fazer em C, ela tem um uso imensamente útil. Você pode aprender a usar scanf e amigos lendo e entendendo o manual . Se você não conseguir passar por esse manual sem problemas sérios de compreensão, isso provavelmente indicaria que você não conhece bem o C.


    scanf e os amigos sofriam com escolhas de design infelizes que dificultavam (e às vezes impossibilitavam) o uso correto sem ler a documentação, como outras respostas mostraram. Isso ocorre em todo o C, infelizmente, por isso, se eu fosse aconselhar contra o uso do scanf , eu provavelmente não recomendaria o uso do C.

    Uma das maiores desvantagens parece ser a pura reputação obtida entre os não iniciados ; Como com muitos resources úteis de C, devemos estar bem informados antes de usá-lo. A chave é perceber que, assim como o resto de C, parece sucinto e idiomático, mas isso pode ser sutilmente enganoso. Isso é difundido em C; É fácil para os iniciantes escreverem códigos que eles acham que fazem sentido e podem até funcionar para eles inicialmente, mas não faz sentido e podem falhar catastroficamente.

    Por exemplo, os não iniciados geralmente esperam que o delegado %s faça com que uma linha seja lida e, embora isso possa parecer intuitivo, isso não é necessariamente verdade. É mais apropriado descrever o campo lido como uma palavra . A leitura do manual é altamente recomendada para todas as funções.

    Qual seria a resposta a essa pergunta sem mencionar sua falta de segurança e risco de transbordamento de buffer? Como já falamos, o C não é uma linguagem segura e nos permitirá reduzir custos, possivelmente para aplicar uma otimização às custas de correção ou, mais provavelmente, porque somos programadores preguiçosos. Assim, quando sabemos que o sistema nunca receberá uma cadeia maior que um número fixo de bytes, temos a capacidade de declarar uma matriz desse tamanho e antecipar a verificação de limites. Eu realmente não vejo isso como uma queda; é uma opção. Mais uma vez, a leitura do manual é altamente recomendada e nos revelaria essa opção.

    Programadores preguiçosos não são os únicos afetados pelo scanf . Não é incomum ver pessoas tentando ler valores float ou double usando %d , por exemplo. Eles geralmente se enganam ao acreditar que a implementação realizará algum tipo de conversão nos bastidores, o que faria sentido porque conversões semelhantes ocorrem em todo o restante do idioma, mas esse não é o caso aqui. Como eu disse anteriormente, scanf e amigos (e na verdade o resto de C) são enganosos; eles parecem sucintos e idiomáticos, mas não são.

    Programadores inexperientes não são forçados a considerar o sucesso da operação . Suponha que o usuário insira algo totalmente não numérico quando scanf ao scanf para ler e converter uma seqüência de dígitos decimais usando %d . A única maneira de interceptarmos esses dados errados é verificar o valor de retorno e com que frequência nos incomodamos em verificar o valor de retorno?

    Muito parecido com fgets , quando scanf e amigos não conseguem ler o que é dito para ler, o stream será deixado em um estado incomum; – No caso de fgets , se não houver espaço suficiente para armazenar uma linha completa, o restante da linha não lida poderá ser erroneamente tratada como se fosse uma nova linha quando não é. – No caso de scanf e amigos, uma conversão falhou conforme documentado acima, os dados errados não são lidos no stream e podem ser tratados erroneamente como se fizessem parte de um campo diferente.

    Não é mais fácil usar scanf e amigos do que usar fgets . Se verificarmos o sucesso procurando por um '\n' quando estamos usando fgets ou inspecionando o valor de retorno quando usamos scanf e amigos, e descobrimos que lemos uma linha incompleta usando fgets ou falhamos em ler um usando o scanf , então estamos diante da mesma realidade: é provável que descartemos a input (geralmente até e incluindo a próxima nova linha)! Yuuuuuuck!

    Infelizmente, o scanf simultaneamente torna difícil (não intuitivo) e fácil (poucas teclas digitadas) descartar a input dessa maneira. Diante dessa realidade de descartar a input do usuário, alguns tentaram scanf("%*[^\n]%*c"); , sem perceber que o delegado %*[^\n] falhará quando encontrar apenas uma nova linha e, portanto, a nova linha ainda será deixada no stream.

    Uma pequena adaptação, separando os dois formatos de delegates e vemos algum sucesso aqui: scanf("%*[^\n]"); getchar(); scanf("%*[^\n]"); getchar(); . Tente fazer isso com tão poucas teclas digitadas usando alguma outra ferramenta;)

    Muitas respostas aqui discutem os potenciais problemas de estouro do uso de scanf("%s", buf) , mas a mais recente especificação POSIX mais ou menos resolve esse problema fornecendo um caractere de alocação de atribuição m que pode ser usado em especificadores de formato para c , s e [ formatos. Isso permitirá que o scanf aloque a quantidade de memory necessária com o malloc (portanto, ele deve ser liberado mais tarde com free ).

    Um exemplo de seu uso:

     char *buf; scanf("%ms", &buf); // with 'm', scanf expects a pointer to pointer to char. // use buf free(buf); 

    Veja aqui As desvantagens dessa abordagem é que ela é uma adição relativamente recente à especificação POSIX e não é especificada na especificação C, portanto, permanece um pouco não suportável por enquanto.