- 締切済み
未初期化変数の扱い方についての質問
Microsoft Visual C++ 2008を使用しています。 #include <stdio.h> int main() { float a; … for(;;) { … scanf("%f",&a); … printf("%f",a); } } このようなプログラムを書いて、実行しました。 aという変数に数字を入力すれば問題ないのですが、誤って文字を入力してしまうと、-107374176という値が連続で出力されます。(for文のループが無限に繰り返される。) for文ではあるキーワードを入力するとループから出るというプログラムになっています。 間違って文字を入力する時にどの文字を入れても必ず、-107374176の値になるので調べたら、「-107374176は0xccccccccでvcのデバッグビルド時の未初期化変数の値と一致する。」という内容をネットで見つけました。 変数として定義されていて、初期化されていない変数を未初期化変数ということなので、aが未初期化変数になるのはわかるのですが、なぜループが無限につづくのかが理解できません。 質問の回答をよろしくお願いします。
- みんなの回答 (6)
- 専門家の回答
みんなの回答
- 和泉 博(@hiroshi09s)
- ベストアンサー率54% (59/109)
#4です。readlineのURLを添付しておきます。また、期待通りかはわかりませんが、回避プログラムを上げておきます。単なる returnは、0.00が入るようになっています。試してみてください。 /* Sample program with input() of float type by Mac OSX * file name: goku.c * compile: gcc goku.c * execution: ./a.out */ #include <stdio.h> #include <stdlib.h> /* atof() */ #define BUFF_SIZE 32 #define ERR_COMMENT "Try again!!" void input_float(char *, float *); int main(void) { float a, b; for(;;) { input_float("Float1: val=", &a); input_float("Float2: val=", &b); if ( a == 0.0 && b == 0.0) break; printf((b < 0.0 ? "%.3f %.3f = %.3f\n" : "%.3f + %.3f = %.3f\n"), a, b, a + b); } return 0; } void input_float(char *comment, float *hensu) { char buff[BUFF_SIZE], *temp; int c, flag; do { printf("%s", comment); temp = fgets(buff, BUFF_SIZE, stdin); /* スペースと最初のマイナス符号を許容 */ temp = buff; while (*temp == ' ') temp++; if (*temp == '-') temp++; /* 実数の判定 */ while ((c = *temp++) != '\0') { flag = (c >= '0' && c <= '9' || c == '.' || c == '\n') ? 0 : -1; if (flag != 0) { temp = buff; while (*temp != '\n') temp++; *temp = '\0'; printf("\t\"%s\"?? %s\n", buff, ERR_COMMENT); break; } } } while (flag != 0); *hensu = atof(buff); }
- notnot
- ベストアンサー率47% (4900/10359)
#3です。 >fgets(line,sizeof line,stdin) != EOFについてですが、fgets()がEOFになるのはどういう状況なのでしょうか? すみません!!!!!書きまちがいです。NULL が正しい。従って、 if(fgets(line,sizeof line,stdin)!=NULL){ になります。 ※私の好みは、if(fgets(line,sizeof line,stdin)){ ですが。 ついでに、#4の方の書いた char *readline(const char *prompt); という関数は、promptの文字列を表示した後、一行入力してそのデータをmallocしたエリアにセットしてそのポインタを返す関数です。Windowsにはたぶん無いと思います。 readlineだと毎回内部でmallocされるので、呼び出し後にfreeが必要です。なので、#4の方のプログラムそのままだとfreeしてないので、ループするたびに使用メモリが増加していきます。また、sprintfでバッファオーバーフローが起こりえます。書き直すと、 float a; char *b; while(b=readline("input? ")){ /* EOFならNULLが返る */ if(sscanf("%f",&a)==1){ ・・・ }else{ 数字じゃないときの処理 } free(b); } Windowsの場合は、コマンドプロンプトのCMD.EXEが行編集機能を持っているので、普通にfgetsでいいです。
- 和泉 博(@hiroshi09s)
- ベストアンサー率54% (59/109)
Linux あるいは Mac OSXの UNIX系で使われるscanf() 関数は承知のとおり標準入力パスからバイトス列を読み込むようになっており、その使い方はファイルからリダイレクトして使う、あるいはパイプラインを使って読み込むようなときに使われます。いわゆる入出力のパターンが決まっているような場合です。特にパイプライン処理を使う場合はバッファリングは必須です。そのような関係から、ターミナルの入力に対してはバッファリングがアダとなって使いづらい非常に不親切な関数になってしまっているように思います。 ターミナルの端末からは、あまり知られていませんが(というかネットには出て来ない)、UNIX系の gccには readline() というターミナルから読み込むことを前提に考えられた関数があります。理由は、↑↓→←のカーソルキーが使えて、任意カーソル位置の文字を削除したり訂正できたりするからです。 そのような隠れた関数が「Microsoft Visual C++ 2008」にもあるように思うのですが、どうなのでしょうか? (注:ソースのスペースは倍角スペースを使っていますので、半角にしてください。) /* UNIX readline() C function of GCC test program by Mac OSX * file name: test.c * compile: gcc test.c -lreadline * execution: ./a.out */ #include <stdio.h> #define input(comment, buffer) sprintf(buffer, "%s", readline(comment)) int main(void) { char a[32]; /* loop end: push "return" key */ while(input("Input n? ", a)) printf(">%s?n", a); // 入力された文字列をチェックし、atof()などで変換 return 0; }
- notnot
- ベストアンサー率47% (4900/10359)
原因は他の方が書かれた通りです。scanfは使い方が非常に難しい関数なので、挙動を全部把握した人でないと使わない方がいいです。つまり初心者は使ってはいけません。 普通は、fgets + sscanf 等を使います。 float a; char line[100]; for(;;){ ... if(fgets(line,sizeof line,stdin)!=EOF){ if(sscanf(line,"%f",&a)==1){ ... printf("%f",a); ... } else { 数字じゃないときの処理 } } else { 入力の終わりの時の処理 } ... }
補足
返信遅くなりました。 ご回答ありがとうございます。 fgets(line,sizeof line,stdin) != EOFについてですが、fgets()がEOFになるのはどういう状況なのでしょうか? 素人なので、その部分が理解できません。 お手数をかけますがよろしくお願いします。
- isle
- ベストアンサー率51% (77/150)
scanfは変換できなかった文字列を内部バッファに残します。 scanfが実行されるときバッファに文字列が残っているとそれを入力された文字列として処理します。 ところが前回変換できなかった文字列ですから今回も変換できずにスルーされバッファの中身もそのままです。 ということを繰り返すのです。 これを防ぐには、 1.scanf("%f",&a)の戻り値を調べて変換できなかったときは中断する。 2.再びscanf("%f",&a)を実行する前にバッファに残っている文字列を読み捨てる。 のいずれかを行ってください。
お礼
回答ありがとうございます。 バッファに残ったままになるんですね。
- anmochi
- ベストアンサー率65% (1332/2045)
scanfは標準入力からの入力を(今回で言えば)floatと仮定して解析して変数aに放り込む関数です。で、アルファベットなどが入ってくるとscanfは当然失敗しますが、その時に標準入力からの入力がクリアされません。なので、 1.標準入力は"aaa"。 2.scanfでfloatと仮定。 3.当然失敗。 4.標準入力は未取り込みの状態に戻るので"aaa"のまま。 5.2に戻る。 となってぐるぐるぐるぐる回り続けているのではないかと推測されます。 これを回避するには、今回の話ではscanfが失敗すると0が返されるので、0が返ってきたらgetsやchar dummy[256]; scanf("%s", &dummy);などを行って標準入力から取り除いてあげると良いのではないでしょうか。
お礼
回答ありがとうございました。 ストリームにデータが残ったままになるのですね。 上書きされるものだと思っていたのですがそういうことではないようですね。ありがとうございます。gets()を入れることで解消されました。ありがとうございます。
補足
ご回答ありがとうございます。 返信が遅くなりました。 まったくの初心者なので、ほとんど理解できませんでした。 commentとbufferはポインタと考えてよろしいのでしょうか? だとすると、そのデータはどこにあるのでしょうか? sprintf(buffer,"%s",readline(comment))というのはcommentというポインタが示す文字列をbufferに書き込むということでしょうか? だとするとreadline(comment)はcommentだけでいいですよね。 申し訳ないんですけど、まったく読むことができなくて混乱しています。 よろしければ、プログラムの部分を詳しく教えてください。