Devo usar fgets ou scanf com entrada limitado em c?

0

Pergunta

Devo usar fgets ou formatado scanf como scanf("%10s", foo).

Exceção que scanf não leia os caracteres em branco, que pode ser resolvido e fazer mais coisas com scanset, então por que eu deveria usar fgets em vez de scanf?

Qualquer ajuda seria apreciada.


Editar

Mais uma coisa que eu quero perguntar é o seguinte: mesmo quando usamos fgets o que aconteceria se o usuário digitar os caracteres mais do que o limite (eu quero dizer um monte de personagens), não é levar a estouro de buffer? Então, como lidar com isso?

c fgets input scanf
2021-11-23 13:53:00
4

Melhor resposta

5

Na maioria dos sistemas operacionais, a entrada do usuário, por padrão, a linha de base. Uma razão para isso é permitir que o usuário pressione a tecla backspace para corrigir a entrada, antes de enviar a entrada para o programa.

Para a linha baseado em entrada do usuário, é significativa e intuitivo para um programa para ler uma linha de entrada de cada vez. Isto é o que a função fgets não (desde que o buffer é grande o suficiente para armazenar toda a linha de entrada).

A função scanfpor outro lado, normalmente não se lê uma linha da entrada de cada vez. Por exemplo, quando você usar o %s ou %d conversão de especificador de formato com scanf, não irá consumir toda uma linha de entrada. Em vez disso, ele só irá consumir tanto de entrada como correspondências a conversão de especificador de formato. Isso significa que o caractere de nova linha no final da linha, normalmente, não podem ser consumidas (que pode facilmente levar a erros de programação). Também, scanf chamado com o %d conversão de especificador de formato irá considerar a entrada, tais como 6sldf23dsfh2 como a entrada válida para o número 6, mas todas as chamadas mais para scanf com o mesmo especificador de falhará, a menos que você descartar o restante da linha da sequência de entrada.

Este comportamento de scanf é contra-intuitiva, considerando que o comportamento de fgets é intuitivo, quando se tratar de linha de base de entrada do usuário.

Depois de usar fgets, você pode usar a função de sscanf na seqüência, para analisar o conteúdo de uma linha individual. Isso permitirá que você continue usando scansets. Ou você pode analisar a linha por outros meios. De qualquer forma, desde que você está usando fgets em vez de scanf para ler a entrada, você vai estar lidando com uma linha de entrada de cada vez, que é a forma natural e intuitiva de lidar com os baseado na linha de entrada do usuário.

Quando usamos fgets o que aconteceria se o usuário digitar os caracteres mais do que o limite (eu quero dizer um monte de personagens), não é levar a estouro de buffer? Então, como lidar com isso?

Se o usuário digitar mais caracteres do que caber na memória intermédia, conforme especificado pelo segundo fgets argumento da função, então ele não vai estouro de buffer. Em vez disso, ele irá extrair somente como muitos personagens da sequência de entrada, como caber no buffer. Você pode determinar se a toda a linha foi lido por verificar se a string contiver um caractere de nova linha '\n' no final.

2021-11-23 15:13:39

Muito obrigado, sua resposta realmente útil para mim.
Becker

O comportamento do fgets() é apenas intuitivo, para entradas que não são mais do que o esperado.
supercat
2

Isso é comumente discutido tópico, cheia de opinião, mas interessante, nenhum a menos. Tenho observado que uma grande maioria daqueles que já respondeu a perguntas semelhantes sobre este site cair do lado de fgets(). Eu sou um deles. Eu encontrar fgets() para ser muito melhor utilização para o usuário de entrada de scanf() com poucas exceções. scanf() é considerado por muitos como sub-ótimo método para o tratamento de usuário de entrada. Por exemplo

"...ele vai dizer se ele conseguiu ou não, mas posso dizer apenas aproximadamente onde ele falhou, e não como ou por quê. Você tem muito pouca oportunidade para fazer qualquer recuperação de erro."
(jamesdlin). Mas, no interesse da tentativa de equilíbrio, vai começar citando essa discussão.

Para a entrada do usuário que vem de stdin, i.e. a entrada do teclado, fgets() vai ser uma melhor escolha. É muito mais indulgente em que a cadeia de lê-lo pode ser validados antes da conversão é tentada

Uma das poucas vezes através de um formulário de scanf(): fscanf() ficaria bem usar pode ser ao converter a entrada de um muito controlado de origem, por exemplo, a leitura de uma estritamente de arquivo formatado com a repetição previsível campos.

Para uma discussão mais, esta comparação dos dois destaques adicionais, vantagens e desvantagens de ambos.

Editar: para endereço OP adicionais pergunta sobre estouro:

"Mais uma coisa que eu quero perguntar é: até quando podemos usar fgets o que aconteceria se o usuário digitar os caracteres mais do que o limite (eu quero dizer um monte de caracteres), não é levar a estouro de buffer? Então, como lidar com ele?"

[fgets()](https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm está muito bem concebido para evitar estouro de buffer, simplesmente usando seus parâmetros corretamente, por exemplo:

char buffer[100] = {0};
...
while fgets(buffer, sizeof buffer, stdin);  

Isso impede a entrada maior do que o tamanho do buffer a ser processado, evitando assim o excesso.

mesmo usando scanf()prevenção de estouro de buffer é bastante simples: Usar um especificador de largura na cadeia de caracteres de formato. Se você quiser ler a entrada, por exemplo, e o limite de tamanho de entrada do usuário para 100 caracteres, no máximo, o código deve incluir o seguinte:

char buffer[101] = {0};// includes space for 100 + 1 for NULL termination

scanf("%100s", buffer);
        ^^^  width specifier 

No entanto, com números, o excesso não é tão agradável usando scanf(). Para o demonstrar, utilizar este código simples, introduzir os dois valores indicados no comentário de um por executar:

int main(void)
{
    int val = 0;
    // test with 2147483647 & 2147483648
    scanf("%d", &val);
    printf("%d\n", val);
    
    return 0;
}

Para o segundo valor, o sistema lança o seguinte:

NON-FATAL RUN-TIME ERROR: "test.c", line 11, col 5, thread id 22832: Function scanf: (errno == 34 [0x22]). Range error `

Aqui você precisa ler em uma seqüência de caracteres e, em seguida, siga com uma seqüência de caracteres para o número de conversão usando um dos strto_() funções: strtol(), strtod(), ...). Ambos incluem a capacidade de teste de estouro de antes , causando um tempo de execução de aviso ou de erro. Note que a utilização de atoi(), atod() não vai proteger contra sobrecarga de qualquer um.

2021-11-23 14:10:20

Obrigado, eu realmente aprecio a sua resposta.
Becker

Uma questão de opinião"? Devo respeitosamente discordo. Não é uma questão de opinião que scanf é quase completamente inútil, bom a melhor para a leitura simples e único, entradas em intro-a-programas em C, mas muito difícil de fazer qualquer coisa remotamente sofisticado com — esses são fatos óbvios! :-)
Steve Summit
1

Até agora, todas as respostas aqui apresentadas meandros da scanf e fgets, mas o que eu acredito é que vale a pena mencionar, é que ambas as funções são reprovados no atual C padrão. Scanf é especialmente perigoso, porque ele tem todos os tipos de problemas de segurança com estouros de buffer. fgets não é tão problemático, mas pela minha experiência, tende a ser um pouco estranho e não tão útil na prática.

A verdade é que, muitas vezes, você realmente não sabe por quanto tempo a entrada do usuário será. Você pode contornar isso usando fgets com eu espero que este será grande o suficiente buffer, mas que não é realmente o resort elegante. Em vez disso, o que muitas vezes você quer fazer é ter dinâmica de memória intermédia que vai crescer para ser grande o suficiente para armazenar qualquer entrada do usuário vai entregar. E isso é quando getline função entra em jogo. Ele é usado para ler qualquer número de caracteres a partir do usuário, até o \n é encontrado. Essencialmente, ele carrega toda a linha para sua memória, como uma seqüência de caracteres.

size_t getline(char **lineptr, size_t *n, FILE *stream);

Esta função recebe um ponteiro para uma string alocado dinamicamente , como primeiro argumento, e um ponteiro para um tamanho de buffer alocado como um segundo argumento e o fluxo como um terceiro argumento. (você irá colocar stdin lá para o comando de linha de entrada). E devolve o número de caracteres de leitura, incluindo o \n no final, mas não a terminação nula.

Aqui, você pode ver um exemplo do uso desta função:

int main() {

printf("Input Something:\n");  // asking user for input

size_t length = 10;                   // creating "base" size of our buffer
char *input_string = malloc(length);  // allocating memory based on our initial buffer size
size_t length_read = getline(&input_string, &length, stdin);  // loading line from console to input_string
// now length_read contains how much characters we read
// and length contains new size of our buffer (if it changed during the getline execution)

printf("Characters read (including end of line but not null at the end)"
       ": %lu, current size of allocated buffer: %lu string: %s"
       , length_read, length, input_string);

free(input_string);    // like any other dynamically-allocated pointer, you must free it after usage
return 0;
}

É claro que, utilizando esta função requer o conhecimento básico sobre ponteiros e dinâmica de memória em C, porém um pouco mais complicado natureza de getline é definitivamente vale a pena, porque a segurança e a flexibilidade.

Você pode ler mais sobre esta função e outras funções de entrada disponíveis em C, neste site: https://www.studymite.com/blog/strings-in-c Eu acredito que resume os meandros da C de entrada muito bem.

2021-11-23 19:18:00

Obrigado por seus conselhos e o link, ele me ajuda muito.
Becker
1

Se você tiver por exemplo uma matriz de caracteres declarado como

char s[100];

e quero ler uma seqüência de caracteres que contém espaços em seguida, você pode usar um scanf o seguinte maneira:

scanf( "%99[^\n]", s );

ou fgets como:

fgets( s, sizeof( s ), stdin );

A diferença entre essas duas chamadas é que a chamada de scanf não lê o caractere de nova linha '\n' a partir do buffer de entrada. Enquanto fgets lê o caractere de nova linha '\n' se não houver espaço suficiente na matriz de caracteres.

Para remover o caractere de nova linha '\n' que é armazenado na matriz de caracteres após o uso fgets você pode, por exemplo, escrever:

s[ strcspn( s, "\n" ) ] = '\0';

Se a cadeia de entrada tem mais de 99 caracteres, em seguida, o chama só de leitura de 99 caracteres e acrescentar a seqüência de caracteres com a terminar o caractere zero '\0'. Todos os caracteres restantes serão ainda no buffer de entrada.

Há um problema com fgets. Por exemplo, se antes de fgets não é usado scanf como por exemplo:

scanf( "%d", &x );
fgets( s, sizeof( s ), stdin );

e a entrada do usuário é:

10
Hello World

em seguida, a chamada de fgets vai ler apenas o caractere de nova linha '\n' que é armazenado no buffer depois de pressionar a tecla Enter quando o valor inteiro na chamada de scanf foi lido.

Neste caso, você precisa escrever um código que irá remover o caractere de nova linha '\n' antes de chamar fgets.

Você pode fazer isso, por exemplo, o seguinte maneira:

scanf( "%d", &x );
scanf( " " );
fgets( s, sizeof( s ), stdin );

Se você estiver usando o scanf então, em tal situação, você pode escrever:

scanf( "%d", &x );
scanf( " %99[^\n]", s );
       ^^ 
2021-11-23 14:05:15

Em outros idiomas

Esta página está em outros idiomas

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................