web-dev-qa-db-ja.com

このコードでscanf()が無限ループを引き起こすのはなぜですか?

ループサイクルごとに1つずつ、標準入力から数値を読み取る小さなCプログラムがあります。ユーザーがNaNを入力すると、エラーがコンソールに出力され、入力プロンプトが再び返されます。 「0」を入力すると、ループが終了し、指定された正/負の値の数がコンソールに出力されます。プログラムは次のとおりです。

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

私の問題は、「a」などの非数値を入力すると、「-> Err ...」を何度も繰り返し記述する無限ループになることです。 scanf()の問題だと思いますが、この関数はより安全なものに置き換えることができますが、この例は初心者向けで、printf/scanf、if-else、loopについて知っています。

この質問 への回答を読んで、他の質問をざっと読みましたが、この特定の問題に実際に答えているものはありません。

45
user208785

scanfは、フォーマット文字列に一致する入力のみを消費し、消費された文字数を返します。フォーマット文字列と一致しない文字があると、スキャンが停止し、無効な文字がバッファに残ったままになります。他の人が言ったように、続行する前に、バッファから無効な文字をフラッシュする必要があります。これはかなり汚い修正ですが、出力から問題のある文字を削除します。

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:すべての非数値文字を一度に削除するようにコードを修正しました。数値ではない文字ごとに複数の「Err」を出力しなくなりました。

ここ はscanfの概要です。

36
jergason

ループを続行する前にバッファをフラッシュする必要があると思います。私はここから書いているものをテストすることはできませんが、そのような何かがおそらく仕事をするでしょう:

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

同様の問題がありました。 scanfのみを使用して解決しました。

Input "abc123<Enter>"仕組みを確認します。

#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

他の回答で指摘されたscanfの問題のため、別のアプローチを検討する必要があります。 scanfの方法は、深刻な入力の読み取りと処理には制限が多すぎることを常に認識しています。 fgetsを使用して行全体を読み取り、strtokstrtol(BTWが整数を正しく解析して正確な場所を正確に伝える)などの関数を使用して作業することをお勧めします無効な文字が始まります)。

3
Eli Bendersky

一部のプラットフォーム(特にWindowsおよびLinux)では、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

scanf()を使用して無効な文字を持つバッファーを処理する必要があるのではなく、fgets()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

解決策:fflush(stdin);を追加する必要があるとき0scanfから返されます。

理由:エラーが発生するとバッファに入力文字が残っているように見えるため、scanfが呼び出されるたびに無効な文字を処理しようとし続けますが、削除はしません。バッファを形成します。 fflushを呼び出すと、入力バッファ(stdin)がクリアされるため、無効な文字は繰り返し処理されなくなります。

You Program Modified:以下は、必要な変更でプログラムを修正したものです。

#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

問題を部分的に解決するために、scanfの後に次の行を追加します。

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

以下に、次の行を含むコード:

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

ただし、複数の文字を入力すると、プログラムは「\ n」まで1文字ずつ続けます。

だから私はここで解決策を見つけました: scanfで入力長を制限する方法

次の行を使用できます。

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

数字以外を入力するとエラーが発生し、数字以外は入力バッファに保持されます。スキップしてください。また、たとえば1aは最初は1番として読み込まれます。このような入力もスキップする必要があると思います。

プログラムは次のようになります。

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

プログラムの出力は次のようになります

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

Read 3 positive and 3 negative numbers
0

私は同じ 問題 を持っていて、ややハックな解決策を見つけました。 fgets()を使用して入力を読み取り、それをsscanf()にフィードします。これは、無限ループの問題に対する悪い修正ではなく、単純なforループを使用して、数字以外の文字を検索するようにCに指示します。以下のコードは、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

これを使ってみてください:

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

これは私のためにうまくいきました...これを試してください。continueステートメントはErr。 1回だけ実行する必要があります。だから、試してみてくださいbreakこれはあなたのためにうまくいきました..私はテストしました....

0
Krishna Chalise