Dois-je utiliser fgets ou scanf avec une participation limitée dans c?

0

La question

Dois-je utiliser fgets ou formaté scanf comme scanf("%10s", foo).

Sauf que scanf ne pas lire les caractères blancs, qui peut être résolu et ne plus se fourre avec scanset, alors pourquoi je devrais utiliser fgets au lieu de scanf?

Toute aide serait appréciée.


Modifier

Une chose que je veux poser est: même lorsque nous utilisons fgets qu'advient-il si l'utilisateur de saisir des caractères plus que limite (je veux dire que beaucoup de caractères), de débordement de la mémoire tampon? Ensuite, la façon de traiter avec elle?

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

La meilleure réponse

5

Sur la plupart d'exploitation des systèmes de sécurité, la saisie de l'utilisateur est, par défaut, basé sur la ligne. L'une des raisons pour cela est de permettre à l'utilisateur d'appuyer sur la touche retour arrière pour corriger l'entrée, avant d'envoyer les données d'entrée du programme.

Pour la ligne de base de la saisie de l'utilisateur, il est utile et intuitif pour un programme pour lire une ligne d'entrée à la fois. C'est ce que la fonction fgets n' (à condition que le tampon est assez grand pour stocker l'ensemble de la ligne de l'entrée).

La fonction scanfd'autre part, il n'est normalement pas lu une ligne de l'entrée à la fois. Par exemple, lorsque vous utilisez le %s ou %d conversion spécificateur de format avec scanfil ne sera pas consommer la totalité d'une ligne de saisie. Au lieu de cela, il ne consommer autant d'entrée correspond à la conversion spécificateur de format. Cela signifie que le caractère de saut de ligne à la fin de la ligne ne sont généralement pas consommés (ce qui peut facilement conduire à des bugs de programmation). Aussi, scanf appelé avec l' %d conversion spécificateur de format considérera les données telles que 6sldf23dsfh2 comme entrée valide pour le nombre 6mais de toute demande en outre à scanf avec le même rédacteur de devis sera un échec, à moins que vous jetez le reste de la ligne à partir du flux d'entrée.

Ce comportement de scanf est contre-intuitive, alors que le comportement de fgets est intuitive, quand il s'agit de la ligne de base de la saisie de l'utilisateur.

Après l'utilisation de fgets, vous pouvez utiliser la fonction sscanf sur la chaîne, de l'analyse du contenu d'une ligne individuelle. Cela vous permettra de continuer à utiliser scansets. Ou vous pouvez analyser la ligne par d'autres moyens. De toute façon, tant que vous êtes en utilisant fgets au lieu de scanf pour la lecture de l'entrée, vous serez la gestion de la ligne de saisie à un moment, qui est le moyen naturel et intuitif pour faire face à la ligne de base de la saisie de l'utilisateur.

Lorsque nous utilisons fgets qu'advient-il si l'utilisateur de saisir des caractères plus que limite (je veux dire que beaucoup de caractères), de débordement de la mémoire tampon? Ensuite, la façon de traiter avec elle?

Si l'utilisateur saisit plus de caractères que l'ajustement dans le tampon spécifié par le deuxième fgets argument de fonction, il ne sera pas de débordement de la mémoire tampon. Au lieu de cela, il ne fait qu'extraire autant de caractères à partir du flux d'entrée en forme dans la mémoire tampon. Vous pouvez déterminer si l'ensemble de la ligne a été lu par vérifier si la chaîne contient un caractère de saut de ligne '\n' à la fin.

2021-11-23 15:13:39

Merci beaucoup, votre réponse vraiment utile pour moi.
Becker

Le comportement de fgets() n'est intuitive pour les entrées qui ne sont pas plus de temps que prévu.
supercat
2

C'est souvent discuté, plein d'avis, mais intéressant néanmoins. J'ai observé qu'une grande majorité de ceux qui ont déjà répondu à des questions similaires sur ce site tomber sur le côté de fgets(). Je suis l'un d'entre eux. Je trouve fgets() être beaucoup mieux d'utiliser pour la saisie de l'utilisateur de scanf() à quelques exceptions près. scanf() est considéré par beaucoup comme sous-optimale de la méthode pour la manipulation de la saisie de l'utilisateur. Par exemple

"...il vous dira s'il a réussi ou échoué, mais peut vous dire seulement approximativement à l'endroit où il a échoué, et pas du tout comment ou pourquoi. Vous ont très peu l'occasion de faire de récupération des erreurs."
(jamesdlin). Mais dans l'intérêt de tenter de l'équilibre, de commencer en citant cette discussion.

Pour la saisie de l'utilisateur qui vient de stdin, c'est à dire la saisie au clavier, fgets() sera un meilleur choix. Il est beaucoup plus indulgent en ce que la chaîne qu'il lit peut être entièrement validée avant de conversion est tenté

L'une des rares fois à l'aide d'un formulaire de scanf(): fscanf() serait d'accord pour utiliser peut-être lors de la conversion d'entrée à partir d'un très contrôlé source, c'est à dire à partir de la lecture d'un strictement fichier au format de répéter prévisible des champs.

Pour une discussion plus approfondie, cette comparaison des deux faits saillants supplémentaires, les avantages et les inconvénients des deux.

Edit: pour répondre à l'OP, la question de savoir à propos de dépassement de capacité:

"Une chose que je veux poser est: même si nous utiliser fgets ce qu'il se passe si l'utilisateur de saisir des caractères plus que limite (je veux dire que beaucoup de caractères), de débordement de la mémoire tampon? Ensuite, la façon de traiter avec il?"

[fgets()](https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm il est joliment conçu pour empêcher le débordement de la mémoire tampon, en utilisant simplement ses paramètres correctement, par exemple:

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

Cela empêche d'entrée supérieure à la taille de la mémoire tampon en cours de traitement, donc de prévenir les débordements.

même en utilisant scanf()la prévention de débordement de la mémoire tampon est assez simple: Utiliser un spécificateur de largeur dans la chaîne de format. Si vous voulez lire d'entrée, par exemple, et la limite de taille d'entrée de l'utilisateur à partir de 100 caractères max, le code devrait inclure les éléments suivants:

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

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

Cependant, avec les numéros, le débordement n'est pas si agréable à l'aide de scanf(). Pour le démontrer, utilisez ce simple code, la saisie des deux valeurs indiquées dans le commentaire de l'un par série:

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

Pour la deuxième valeur, mon système lance le suivant:

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

Ici, vous avez besoin de lire une chaîne de caractères, puis suivez avec une chaîne à nombre de conversion à l'aide de l'un des strto_() fonctions: strtol(), strtod(), ...). Les deux comprennent la capacité de test de débordement avant de provoquer un moment de l'exécution d'avertissement ou d'erreur. Notez que l'utilisation de atoi(), atod() ne protège pas de dépassement de soit.

2021-11-23 14:10:20

Je vous remercie, je vous remercie de votre réponse.
Becker

Une question "plein de opinion"? Je dois respectueusement en désaccord. Ce n'est pas une question d'opinion qui scanf est presque totalement inutile, bien au mieux pour la lecture simple et entrées en intro-à-programmes en C, mais extrêmement difficile à faire quelque chose à distance sophistiquée avec — ces sont faits évidents! :-)
Steve Summit
1

Jusqu'à présent, toutes les réponses ici présenté subtilités de la scanf et fgetsmais je crois que ce qui vaut la peine de mentionner, c'est que ces deux fonctions sont obsolètes en courant en C standard. Scanf est particulièrement dangereux, car il a toutes sortes de problèmes de sécurité avec des débordements de tampon. fgets n'est pas aussi problématique, mais de mon expérience, il a tendance à être un peu maladroit et pas si utile dans la pratique.

La vérité est que, souvent, vous ne savez pas vraiment combien de temps l'entrée de l'utilisateur sera. Vous pouvez contourner ce problème en utilisant fgets avec j'espère que ce sera assez grand tampon, mais ce n'est pas vraiment ellegant. Au lieu de cela, ce que vous voulez souvent à faire est d'avoir la mémoire tampon dynamique qui va pousser à être assez grand pour stocker ce que la saisie de l'utilisateur livrera. Et c'est quand getline fonction entre en jeu. Il est utilisé pour lire n'importe quel nombre de caractères à partir de l'utilisateur, jusqu'à ce que \n est rencontré. Essentiellement, il charge l'ensemble de la ligne pour votre mémoire, comme une chaîne de caractères.

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

Cette fonction reçoit un pointeur vers une allouée dynamiquement chaîne comme premier argument, et un pointeur vers une taille de la mémoire tampon allouée en tant que deuxième argument et de flux comme troisième argument. (vous aura essentiellement lieu stdin il pour de ligne de commande d'entrée). Et retourne le numéro de lire des caractères, y compris le \n à la fin, mais pas la valeur null.

Ici, vous pouvez voir un exemple d'utilisation de cette fonction:

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;
}

Bien sûr, l'utilisation de cette fonction nécessite des connaissances de base sur les pointeurs et gestion dynamique de la mémoire en C, cependant légèrement plus compliqué de la nature de getline est certainement la peine, en raison de la condition de la sécurité et de la flexibilité.

Vous pouvez en lire plus à propos de cette fonction, et d'autres fonctions d'entrée disponibles en C, sur ce site: https://www.studymite.com/blog/strings-in-c Je crois qu'il résume les subtilités de la C d'entrée assez bien.

2021-11-23 19:18:00

Merci pour vos conseils et le lien, cela m'aide beaucoup.
Becker
1

Si vous avez par exemple un tableau de caractères déclaré comme

char s[100];

et que vous voulez lire une chaîne de caractères qui contient des espaces, alors vous pouvez utiliser scanf la façon suivante:

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

ou fgets comme:

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

La différence entre ces deux appels, c'est que l'appel de scanf ne pas lire le caractère de nouvelle ligne '\n' à partir de la mémoire tampon d'entrée. Alors que fgets lit le caractère de nouvelle ligne '\n' si il y a assez d'espace dans le tableau de caractères.

Pour supprimer le caractère de nouvelle ligne '\n' qui est stocké dans le tableau de caractères après l'utilisation de fgets vous pouvez écrire par exemple:

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

Si la chaîne d'entrée est de plus de 99 caractères, puis les deux appels en lecture seulement 99 caractères et ajouter la séquence de caractères avec la terminaison zéro caractère '\0'. Tous les autres caractères seront encore dans la mémoire tampon d'entrée.

Il y a un problème avec fgets. Par exemple, si avant de fgets il est utilisé scanf comme par exemple:

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

et l'entrée de l'utilisateur est:

10
Hello World

ensuite, l'appel de fgets va lire seulement le caractère de nouvelle ligne '\n' qui est stocké dans la mémoire tampon après avoir appuyé sur la touche Entrée lorsque la valeur de l'entier à l'appel de scanf a été lu.

Dans ce cas, vous devez écrire un code qui permet de supprimer le caractère de nouvelle ligne '\n' avant d'appeler fgets.

Vous pouvez le faire par exemple de la manière suivante:

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

Si vous utilisez scanf alors dans une telle situation, vous pouvez écrire:

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

Dans d'autres langues

Cette page est dans d'autres langues

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