• 締切済み

未初期化変数の扱い方についての質問

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が未初期化変数になるのはわかるのですが、なぜループが無限につづくのかが理解できません。    質問の回答をよろしくお願いします。

みんなの回答

回答No.6

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

参考URL:
http://www.fireproject.jp/feature/c-language/etc/readline.html
  • notnot
  • ベストアンサー率47% (4848/10261)
回答No.5

#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でいいです。

回答No.4

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

gokugokuR
質問者

補足

ご回答ありがとうございます。 返信が遅くなりました。 まったくの初心者なので、ほとんど理解できませんでした。 commentとbufferはポインタと考えてよろしいのでしょうか? だとすると、そのデータはどこにあるのでしょうか? sprintf(buffer,"%s",readline(comment))というのはcommentというポインタが示す文字列をbufferに書き込むということでしょうか? だとするとreadline(comment)はcommentだけでいいですよね。 申し訳ないんですけど、まったく読むことができなくて混乱しています。 よろしければ、プログラムの部分を詳しく教えてください。

  • notnot
  • ベストアンサー率47% (4848/10261)
回答No.3

原因は他の方が書かれた通りです。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 {   入力の終わりの時の処理  }  ... }

gokugokuR
質問者

補足

返信遅くなりました。 ご回答ありがとうございます。 fgets(line,sizeof line,stdin) != EOFについてですが、fgets()がEOFになるのはどういう状況なのでしょうか? 素人なので、その部分が理解できません。 お手数をかけますがよろしくお願いします。

  • isle
  • ベストアンサー率51% (77/150)
回答No.2

scanfは変換できなかった文字列を内部バッファに残します。 scanfが実行されるときバッファに文字列が残っているとそれを入力された文字列として処理します。 ところが前回変換できなかった文字列ですから今回も変換できずにスルーされバッファの中身もそのままです。 ということを繰り返すのです。 これを防ぐには、 1.scanf("%f",&a)の戻り値を調べて変換できなかったときは中断する。 2.再びscanf("%f",&a)を実行する前にバッファに残っている文字列を読み捨てる。 のいずれかを行ってください。

gokugokuR
質問者

お礼

回答ありがとうございます。 バッファに残ったままになるんですね。

  • anmochi
  • ベストアンサー率65% (1332/2045)
回答No.1

 scanfは標準入力からの入力を(今回で言えば)floatと仮定して解析して変数aに放り込む関数です。で、アルファベットなどが入ってくるとscanfは当然失敗しますが、その時に標準入力からの入力がクリアされません。なので、 1.標準入力は"aaa"。 2.scanfでfloatと仮定。 3.当然失敗。 4.標準入力は未取り込みの状態に戻るので"aaa"のまま。 5.2に戻る。 となってぐるぐるぐるぐる回り続けているのではないかと推測されます。  これを回避するには、今回の話ではscanfが失敗すると0が返されるので、0が返ってきたらgetsやchar dummy[256]; scanf("%s", &dummy);などを行って標準入力から取り除いてあげると良いのではないでしょうか。

gokugokuR
質問者

お礼

回答ありがとうございました。 ストリームにデータが残ったままになるのですね。 上書きされるものだと思っていたのですがそういうことではないようですね。ありがとうございます。gets()を入れることで解消されました。ありがとうございます。

関連するQ&A

  • 変数の初期化について

    変数の宣言について質問です とあるループするオブジェクトに float bure; bure=sinf(timeGetTime()/1000.0f)/1000; とあるのですが、これはループするたびに「bure」が初期化されますよね。 この「bure」を初期化せずに値を保持したままにする方法などありますでしょうか? グローバルにすれば何とかなると思ったのですが、こんな感じの変数が多いのでゴチャゴチャになってしまいそうです。 解決策はありますでしょうか?

  • 入力した数字の分を配列0~3に・・・

    whileで無限ループを作ってscanfで値を入力していくのですが 入力を例えば 0 1 2 2 3(breakする) と入力した場合に その際に a[0] = 1;(0が1個入力されたので1) a[1] = 1; a[2] = 2; 整数で0より小さくて3より大きい場合は無限ループからbreakで脱出するようなプログラムを作りたいと思っているのですが for文でa[i]でやってカウントしていくのかな・・・?とは思っているのですが 中々うまくいきません 関数、ライブラリ等は使いません。 気になってどうしても手をつけてしまうのですが 1日以上悩んでもどうすべきかわかりません

  • floatの値について

    初心者的質問です。暇な方お答え願います。 分かりやすく教えてくださると嬉しいです。 例えば、簡単な main() { float a; printf("値を入力して下さい ==> "); scanf("%f",&a); printf("入力した値は%fです",a); } という感じで値を入力して出力するプログラムがあったとします。 このとき,例えば値を 166.5と入力すれば、166.500000とでますが 166.7と入力すると、166.699997とでるのはなぜでしょうか? doubleで変数宣言した場合は、このようにならないのですが。

  • 実数型の変数に値を入力した計算結果がおかしくなる理由

    こんばんは。 以下のプログラムで値がおかしくなる理由、改善方法を教えて頂けませんでしょうか。 double a,b,c; scanf("%f",&a); scanf("%f",&b); c = a + b; printf("%f",c); これを実行すると、正しい値が出力されません。 int型で宣言し、整数表示の%dに変更すると問題なく値が出力されます。 また、予めdouble型で宣言した変数a,bに実数の初期値(8.5等)を格納しておくと正しい値が出力されます。 どなたかご回答よろしくお願い致します。

  • グローバル変数の定義について質問です。

    下記は私が作成した簡易サンプルプログラムです。 #include <stdio.h> #include <stdlib.h> #include <math.h> double a=1; double b=2; double c=3; double f(){ double y; scanf("%lf",&y) ; return y; } double g(){ double y; y=f()*a; return y; } void main(){ double y; y=g()*b*c; printf("y = \n",y) ; } このプログラムでは実行しても答えが出ません。 グローバル変数でscanfを使用して入力した値を上記double g()で使用することは不可能なのでしょうか。 また、もし可能な方法があるのでしたら教えていただければ幸いです。 よろしくお願いします。

  • scanf()で、エラー対応

    scanf()を使用して、入力で例えば「5462fa」数字ではなく文字を入力してしまった場合エラー(無限ループ)になりますが、 これをscanf()を使用したまま再入力を促すことが可能でしょうか?よろしくお願いします。 #include <stdio.h> int main(){   int a , kazu;   for(a=0;a<1;){     printf("値入力せよー>");       scanf ("%d", &kazu);         if( kazu >= 1 && kazu <=100 ){           a = a + 1 ;         }else{           printf("1から100で入力せよ\n");         }   }   printf ("kazu = %d", kazu);   return 0; }

  • C++、クラスメンバの構造体配列の初期化について

    クラスのメンバに構造体の配列を持っていて、その構造体はfloat値を4つ持ってます。 私はいつもメンバ変数の初期化はコンストラクタでしています。 クラス生成時に全てのfloatを0で初期化したいのですが、これはコンストラクタでfor文を回すしかないのでしょうか? 一文で初期化することは出来ないのでしょうか? 話は少し変わってしまいますが、今思ったのですがコンストラクタで値を決めるのは初期化じゃなくて代入と呼ぶのですかね? ご助力よろしくお願いします。

  • PHP 変数の初期化について教えてください

    PHPを勉強中の者です。他のプログラム言語の使用経験は一切ありません。 PHPにおいては変数の初期化は絶対に必要ではないということで、今まで特に気にすることは無かったのですが、マニュアルにもあるように初期化することは良い習慣とのことで正しい初期化を知りたく質問させていただきます。 他のプログラムにおいては最も最初に解決すべき部分であり、恥を晒すようですが、なにとぞアドバイスをいただけたらと思います。 以下質問になります。 ---------------------------------- 1.私の解釈では変数の初期化とは、変数の型を決め初期値を入れる、と認識していますが、これは正しいのでしょうか。 2.Javaの参考書で変数の部分を読んでみたところ、型を決めただけでは初期化とはいわず、型決めと初期値を入れた工程(工程ってのもおおげさかもしれませんが)が初期化と記載がありました。 PHPにおいては型の宣言は不要とのことで下記のように記載すれば型及び初期値が決まると解釈していますが、この解釈は正しいのでしょうか? $a = 1;  //これは整数 $d = 1.1 //これは浮動小数点 $b = "aa"; //これは文字列 $c = true; //これは論理値 $a = array(); //これは配列 3.他の参考になりそうなサイトで配列を代入する変数を初期化する場合として下記のような記載がありました。 unset($a); $a = array("aa", "bb"); unsetは変数を解放するためのものであり、私だったらこの場合の初期化として $a = array(); を書く方が適しているように思いますが、配列を代入する変数を初期化する場合、上記サンプルと私の考えとどちらが正しいのでしょうか? 4.変数を初期化するタイミングについて 質問のために、サンプルコードを書きました。まず下記サンプルを見てください。 ※このサンプルコードはformエリアからの入力値によって計算を行なうものと想定して下さい。値を受けた後の処理工程を記載しています。また、受ける値の正当性チェックについて省いています。 (1)(2)については質問箇所です。 (1) if(isset($_GET['send'], $_GET['int1'], $_GET['int2'])){ (2) $a = $_GET['int1']; $b = $_GET['int2'] }else{ exit; } $c = $a + $b; echo $c; サンプルコードは以上となります。仮にこういうコードがあったとして、初期化するタイミングがわかりません。コード中に(1)(2)と番号を振りましたが、個人的にこの2箇所のどちらかで初期化すればよいだろうと思い込んでいますが、こういうコードの場合、どの部分で初期化した方がいいのでしょうか? ---------------------------------- 質問は以上となります。 何卒宜しくおねがいします。

    • ベストアンサー
    • PHP
  • プログラミング float型

    float型の変数x、yを定義し、scanfを使ってキーボードから値を入力して代入する。この2個の変数を引数として平均値を計算し、その値をfloat値として返す関数heikinを作成し、mainから呼び出し、結果を表示する表示するプログラムを作成するという命令なのですが、このプログラムのソースを教えていただけないでしょうか?

  • C言語・値交換について

    キーボードから10個のfloat型の値を入力し、入力した順序と逆順に表示するプログラムを作成せよ。 との問題ですが解けずに悔しいです、アドバイスお願いします。 // charを使うと思いますが練習不足です><。 #include<stdio.h> int main() { int i; float a[10]; for(i=0;i<=10;i=i-1){ scanf("%f",&a[i]); printf("%f\n",a[i]); } for(i=0;i<=10;i++){ printf("Input=\n"); } return 0; }

専門家に質問してみよう