scanfでループする
以下のコードは不正な入力(例えばabc)を与えると無限ループします.
/* scanfに不正入力をするとループするテスト */ #include <stdio.h> #include <stdlib.h> int main(void){ int x = 0; while(true){ printf("Please input x\n"); scanf("%d", &x); printf("x = %d\n", x); if(x == -1) break; } return EXIT_SUCCESS; }
この原因はscanfが予期しないデータの入力によって,
バッファのデータをそのまま残して動作を終了していることにあります.
scanfは予期しない入力があると無限ループに陥る(C学習中) - 虎塚
変換指定文字列で、期待していなかったデータを入力すると、 バッファのデータをそのまま残し、動作を終了してしまいます。
fflush(stdin);
scanf("%*c");
このあたりも試してバッファのクリアを狙ってみましたが,思うような動作には至りませんでした.
fgetsのあとatoiする
// 独自関数fgetiを実装する // ほぼ問題ないがatoiが範囲外の処理ができない #include<stdio.h> #include<stdlib.h> #include<string.h> #include<limits.h> #define N 512 int fgeti(int*, FILE*); int main() { int x; while(true) { printf("Please input a number\n"); if (fgeti(&x, stdin) == EOF) { continue; } printf("x = %d\n", x); } } int fgeti(int* p, FILE *fp) { char buf[N]; if (fgets(buf, N, fp) == NULL) { return EOF; } *p = atoi(buf); if(*p == 0 && (strcmp(buf, "0\n")!=0)) { return EOF; } return 1; }
こうすることで大抵の処理には耐えますが,範囲(INT_MIN〜INT_MAX)外と
123abcのような頭に数字のついた入力に耐えません.
文字列から数値への変換 - forest book
なお,atoiは変換できない文字列の際に0を返すことにも注意です.
【C言語】atoi関数|ato関数群(atoi, atol, atoll, atof)完全解説 | MaryCore
fgetsのあとstrtolする
atoiよりエラーに強いstrtolを使って
strtoiという関数を定義しました.
strtolは第二引数のendptrに変換出来ない文字列を格納します.
C言語関数辞典 - strtol
// 独自関数fgetiを実装する // atoiからstrtoi(自作)に変更 // これでどんな入力にも対応できる...はず #include<stdio.h> #include<stdlib.h> #include<string.h> #include<limits.h> #define N 512 int fgeti(int*, FILE*); int strtoi(const char*); int main() { int x; while(true) { printf("Please input a number\n"); if (fgeti(&x, stdin) == EOF) { continue; } printf("x = %d\n", x); } } int fgeti(int* p, FILE *fp) { char buf[N]; if (fgets(buf, N, fp) == NULL) { return EOF; } *p = strtoi(buf); if(*p == 0 && (strcmp(buf, "0\n")!=0)) { return EOF; } return 1; } int strtoi(const char* str) { char* endptr; long lstr = strtol(str, &endptr, 10); if (*endptr != '\0' || lstr < INT_MIN || INT_MAX < lstr) { return 0; } return (int)lstr; }
少なくともscanfで直接受け取るよりは堅牢かと思います.