Pourquoi est scanf() provoquer une boucle infinie dans ce code?

j'ai un petit C-program qui lit les nombres de stdin, un à chaque cycle de boucle. Si l'utilisateur introduit une NaN, une erreur doit être imprimée sur la console et l'invite d'entrée doit revenir. À l'entrée de "0", la boucle doit se terminer et le nombre de valeurs positives/négatives doit être imprimé sur la console. Voici le programme:

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;

    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            printf("Err...n");
            continue;
        }

        if (number > 0) p++;
        else if (number < 0) n++;
        else break; /* 0 given */
    }

    printf("Read %d positive and %d negative numbersn", p, n);
    return 0;
}

mon problème est, qu'en entrant un non-nombre (comme "a"), cela résulte en une boucle infinie d'écriture "-> ERR..."plus et plus. Je suppose que c'est un problème de scanf() et je sais que cette fonction pourrait être remplacée par une plus sûre, mais cet exemple est pour les débutants, connaissant juste au sujet de printf/scanf, if-else et loops.

j'ai déjà lu les réponses à cette question et passé en revue d'autres questions, mais rien ne répond vraiment à ce problème spécifique.

42
demandé sur Abhi 2009-11-11 18:39:29

13 réponses

scanf consomme seulement l'entrée qui correspond à la chaîne de format, en retournant le nombre de caractères consommés. Tout caractère qui ne correspond pas à la chaîne de format l'empêche de numériser et laisse le caractère invalide dans le tampon. Comme d'autres l'ont dit, vous devez toujours vider le caractère invalide du tampon avant de procéder. Il s'agit d'une correction assez sale, mais il va supprimer les caractères offensants de la sortie.

char c = '0';
if (scanf("%d", &number) == 0) {
  printf("Err. . .\n");
  do {
    c = getchar();
  }
  while (!isdigit(c));
  ungetc(c, stdin);
  //consume non-numeric chars from buffer
}

edit: correction du code pour supprimer tous les non-numérique caractères en une seule fois. N'imprimera plus de multiples "Errs"pour chaque caractère non numérique.

ici est un assez bon aperçu de scanf.

35
répondu jergason 2010-08-18 02:16:07

scanf() laisse le" a " toujours dans le tampon d'entrée pour la prochaine fois. Vous devriez probablement utiliser getline() pour lire une ligne n'importe quoi et puis la parse avec strtol() ou similaire à la place.

(Oui, getline() est GNU-spécifique, pas POSIX. Et alors? La question est étiquetée "gcc"et " linux". getline() est également la seule option raisonnable pour lire une ligne de texte, sauf si vous voulez tout faire à la main.)

7
répondu Teddy 2009-11-11 16:33:57

sur certaines plateformes (notamment Windows et Linux) vous pouvez utiliser fflush(stdin); :

#include <stdio.h>

int main(void)
{
  int number, p = 0, n = 0;

  while (1) {
    printf("-> ");
    if (scanf("%d", &number) == 0) {
        fflush(stdin);
        printf("Err...\n");
        continue;
    }
    fflush(stdin);
    if (number > 0) p++;
    else if (number < 0) n++;
    else break; /* 0 given */
  }

  printf("Read %d positive and %d negative numbers\n", p, n);
  return 0;
}
7
répondu nanotexnik 2016-11-07 08:37:14

je pense que vous avez juste à rincer le tampon avant de continuer avec la boucle. Quelque chose comme ça ferait probablement le travail, bien que je ne puisse pas tester ce que j'écris d'ici:

int c;
while((c = getchar()) != '\n' && c != EOF);
6
répondu Lucas 2009-11-11 16:02:48

plutôt que d'utiliser scanf() et de traiter avec le tampon ayant un caractère invalide, utilisez fgets() et sscanf() .

/* ... */
    printf("0 to quit -> ");
    fflush(stdout);
    while (fgets(buf, sizeof buf, stdin)) {
      if (sscanf(buf, "%d", &number) != 1) {
        fprintf(stderr, "Err...\n");
      } else {
        work(number);
      }
      printf("0 to quit -> ");
      fflush(stdout);
    }
/* ... */
4
répondu pmg 2009-11-11 16:00:53

en raison des problèmes avec scanf souligné par les autres réponses, vous devriez vraiment envisager d'utiliser une autre approche. J'ai toujours trouvé scanf bien trop limité pour une lecture et un traitement sérieux des entrées. C'est une meilleure idée de simplement lire des lignes entières avec fgets et de travailler ensuite sur eux avec des fonctions comme strtok et strtol (qui BTW va correctement analyser les entiers et vous dire exactement où les caractères invalides commencent).

3
répondu Eli Bendersky 2009-11-11 15:53:15

j'ai eu le même problème. J'ai résolu en utilisant uniquement le scanf.

Input "abc123<Enter>" pour voir comment ça marche.

#include <stdio.h>
int n, num_ok;
char c;
main() {
    while (1) {
        printf("Input Number: ");
        num_ok = scanf("%d", &n);
        if (num_ok != 1) {
            scanf("%c", &c);
            printf("That wasn't a number: %c\n", c);
        } else {
            printf("The number is: %d\n", n);
        }
    }
}
3
répondu Manel Guerrero 2012-09-14 13:40:37

Salut je sais que c'est un vieux fil mais je viens de finir un devoir scolaire où je suis tombé sur ce même problème. Ma solution est que j'ai utilisé gets() pour récupérer ce que scanf () a laissé derrière.

voici le code OP légèrement réécrit; probablement pas d'utilité pour lui, mais peut-être qu'il aidera quelqu'un d'autre là-bas.

#include <stdio.h>

    int main()
    {
        int number, p = 0, n = 0;
        char unwantedCharacters[40];  //created array to catch unwanted input
        unwantedCharacters[0] = 0;    //initialzed first byte of array to zero

        while (1)
        {
            printf("-> ");
            scanf("%d", &number);
            gets(unwantedCharacters);        //collect what scanf() wouldn't from the input stream
            if (unwantedCharacters[0] == 0)  //if unwantedCharacters array is empty (the user's input is valid)
            {
                if (number > 0) p++;
                else if (number < 0) n++;
                else break; /* 0 given */
            }
            else
                printf("Err...\n");
        }
        printf("Read %d positive and %d negative numbers\n", p, n);
        return 0;
    }
0
répondu midnightCoder 2015-03-16 13:12:03

essayez ceci:

if (scanf("%d", &number) == 0) {
        printf("Err...\n");
        break;
    }

ça a bien marché pour moi... essayez ceci.. la mention continue n'est pas appropriée, car elle constitue l'erreur .. ne doit être exécuté qu'une seule fois. donc, essayez break que j'ai testé... cela a bien fonctionné pour vous.. je l'ai testé....

0
répondu krishna chalise 2016-10-07 05:34:49

Bonsoir. J'ai récemment eu le même problème et j'ai trouvé une solution qui pourrait aider beaucoup de gars. En fait, la fonction "scanf "laisse un tampon en mémoire... et c'est pourquoi la boucle infinie est causé. Vous devez donc "stocker" ce tampon sur une autre variable si votre scanf initial contient la valeur "null". Voilà ce que je veux dire:

#include <stdio.h>
int n;
char c[5];
main() {
    while (1) {
        printf("Input Number: ");
        if (scanf("%d", &n)==0) {  //if you type char scanf gets null value
            scanf("%s", &c);      //the abovementioned char stored in 'c'
            printf("That wasn't a number: %s\n", c);
        }
        else printf("The number is: %d\n", n);
    }
}
0
répondu Drunk Koala 2016-12-14 21:56:50

Lorsqu'un non-nombre est entré, une erreur se produit et le non-nombre est toujours conservé dans le tampon d'entrée. Vous devez sauter. Aussi même cette combinaison de symboles comme par exemple 1a sera lue d'abord comme numéro 1 je pense que vous devriez également sauter une telle entrée.

le programme peut ressembler à la manière suivante.

#include <stdio.h>
#include <ctype.h>

int main(void) 
{
    int p = 0, n = 0;

    while (1)
    {
        char c;
        int number;
        int success;

        printf("-> ");

        success = scanf("%d%c", &number, &c);

        if ( success != EOF )
        {
            success = success == 2 && isspace( ( unsigned char )c );
        }

        if ( ( success == EOF ) || ( success && number == 0 ) ) break;

        if ( !success )
        {
            scanf("%*[^ \t\n]");
            clearerr(stdin);
        }
        else if ( number > 0 )
        {
            ++p;
        }
        else if ( number < n )
        {
            ++n;
        }
    }

    printf( "\nRead %d positive and %d negative numbers\n", p, n );

    return 0;
}

la sortie du programme pourrait ressembler à

-> 1
-> -1
-> 2
-> -2
-> 0a
-> -0a
-> a0
-> -a0
-> 3
-> -3
-> 0

Read 3 positive and 3 negative numbers
0
répondu Vlad from Moscow 2017-02-09 19:55:06

j'ai eu le même problème , et j'ai trouvé une solution un peu hacky. J'utilise fgets() pour lire l'entrée et à donner qu'à sscanf() . Ce n'est pas une mauvaise solution pour le problème de boucle infinie, et avec un simple pour boucle je dis à C de rechercher n'importe quel caractère numérique. Le code ci-dessous n'autorise pas les entrées comme 123abc .

#include <stdio.h>
#include <ctype.h>
#include <string.h>

int main(int argc, const char * argv[]) {

    char line[10];
    int loop, arrayLength, number, nan;
    arrayLength = sizeof(line) / sizeof(char);
    do {
        nan = 0;
        printf("Please enter a number:\n");
        fgets(line, arrayLength, stdin);
        for(loop = 0; loop < arrayLength; loop++) { // search for any none numeric charcter inisde the line array
            if(line[loop] == '\n') { // stop the search if there is a carrage return
                break;
            }
            if((line[0] == '-' || line[0] == '+') && loop == 0) { // Exculude the sign charcters infront of numbers so the program can accept both negative and positive numbers
                continue;
            }
            if(!isdigit(line[loop])) { // if there is a none numeric character then add one to nan and break the loop
                nan++;
                break;
            }
        }
    } while(nan || strlen(line) == 1); // check if there is any NaN or the user has just hit enter
    sscanf(line, "%d", &number);
    printf("You enterd number %d\n", number);
    return 0;
}
0
répondu ilgaar 2017-12-16 12:53:24

rincer le tampon d'entrée avant de numériser:

while(getchar() != EOF) continue;
if (scanf("%d", &number) == 0) {
    ...

j'allais suggérer fflush(stdin) , mais apparemment cela aboutit à comportement non défini .

En réponse à votre commentaire, si vous souhaitez l'invite à se présenter, vous devez vider le tampon de sortie. Par défaut, cela ne se produit que lorsque vous imprimez une nouvelle ligne. Comme:

while (1) {
    printf("-> ");
    fflush(stdout);
    while(getchar() != EOF) continue;
    if (scanf("%d", &number) == 0) {
    ...
-1
répondu Andomar 2009-11-11 16:20:41