web-dev-qa-db-fra.com

Pourquoi scanf () provoque-t-il une boucle infinie dans ce code?

J'ai un petit programme C qui lit juste les nombres de stdin, un à chaque cycle de boucle. Si l'utilisateur entre du NaN, une erreur doit être imprimée sur la console et l'invite d'entrée doit à nouveau s'afficher. A l'entrée de "0", la boucle doit se terminer et le nombre de valeurs positives/négatives données 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 numbers\n", p, n);
    return 0;
}

Mon problème est que, lors de la saisie d'un non-nombre (comme "a"), cela se traduit par une boucle infinie écrivant "-> Err ..." encore et encore. Je suppose que c'est un problème avec scanf () et je sais que cette fonction pourrait être remplacée par une plus sûre, mais cet exemple est destiné aux débutants, connaissant à peu près printf/scanf, if-else et les boucles.

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

45
user208785

scanf ne consomme que l'entrée correspondant à la chaîne de format, renvoyant le nombre de caractères consommés. Tout caractère qui ne correspond pas à la chaîne de format entraîne l'arrêt de l'analyse et laisse le caractère non valide toujours dans le tampon. Comme d'autres l'ont dit, vous devez toujours vider le caractère invalide du tampon avant de continuer. C'est un correctif assez sale, mais il supprimera les caractères incriminés 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 caractères non numériques en une seule fois. N'imprimera plus plusieurs "Errs" pour chaque caractère non numérique.

ici est un assez bon aperçu de scanf.

36
jergason

Je pense qu'il suffit de vider le tampon avant de continuer avec la boucle. Quelque chose comme ça ferait probablement l'affaire, bien que je ne puisse pas tester ce que j'écris à partir d'ici:

int c;
while((c = getchar()) != '\n' && c != EOF);
7
Lucas

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

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

6
Teddy

J'ai eu un problème similaire. J'ai résolu en utilisant uniquement scanf.

Input "abc123<Enter>" pour voir comment cela fonctionne.

#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
Manel Guerrero

En raison des problèmes avec scanf signalés par les autres réponses, vous devriez vraiment envisager d'utiliser une autre approche. J'ai toujours trouvé scanf beaucoup trop limité pour toute lecture et traitement d'entrée sérieux. C'est une meilleure idée de simplement lire des lignes entières avec fgets puis de les travailler avec des fonctions comme strtok et strtol (que BTW analysera correctement les entiers et vous dira exactement où les caractères invalides commencent).

3
Eli Bendersky

Sur certaines plates-formes (en particulier 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;
}
3
nanotexnik

Plutôt que d'utiliser scanf() et d'avoir à traiter le tampon avec 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);
    }
/* ... */
3
pmg

La solution: Vous devez ajouter fflush(stdin); lorsque 0 est renvoyé par scanf.

The Reason: Il semble laisser le caractère d'entrée dans le tampon lorsqu'une erreur est rencontrée, donc chaque fois que scanf est appelé, il continue d'essayer de gérer le caractère invalide mais ne le supprime jamais il forme le tampon. Lorsque vous appelez fflush, le tampon d'entrée (stdin) sera effacé afin que le caractère non valide ne soit plus traité de manière répétée.

Vous programmez modifié: Ci-dessous est votre programme modifié avec le changement nécessaire.

#include <stdio.h>

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

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

        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;
}
1
Cabbage Champion

Pour résoudre en partie votre problème, j'ajoute juste cette ligne après le scanf:

fgetc(stdin); /* to delete '\n' character */

Ci-dessous, votre code avec la ligne:

#include <stdio.h>

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

    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            fgetc(stdin); /* to delete '\n' character */
            printf("Err...\n");
            continue;
        }

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

Mais si vous entrez plus d'un caractère, le programme continue un par un jusqu'à ce que le "\ n".

J'ai donc trouvé une solution ici: Comment limiter la longueur d'entrée avec scanf

Vous pouvez utiliser cette ligne:

int c;
while ((c = fgetc(stdin)) != '\n' && c != EOF);
0
sushi

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

Le programme peut ressembler à ceci.

#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
Vlad from Moscow

J'ai eu le même problème , et j'ai trouvé une solution quelque peu hacky. J'utilise fgets() pour lire l'entrée, puis je l'alimente sur sscanf(). Ce n'est pas une mauvaise solution pour le problème de boucle infinie, et avec une simple boucle for, je dis à C de rechercher tout caractère non 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
ilgaar

essayez d'utiliser ceci:

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

cela a bien fonctionné pour moi ... essayez ceci .. l'instruction continuer n'est pas appropriée car Err .. ne devrait s'exécuter qu'une seule fois. alors, essayez pause que j'ai testé ... cela a bien fonctionné pour vous .. j'ai testé ....

0
Krishna Chalise