- ベストアンサー
数値の入力について
C言語です。 #include <stdio.h> #include <stdlib.h> unsigned long input(void); void main(void){ while(1){ printf("%d\n",input()); } } unsigned long input(void){ unsigned long input; do{ printf("入力(1~4294967294 exit=0>"); scanf("%lu",&input); if(input==0) exit(0); while (getchar() != '\n') {} }while ( ( input < 1 )||( 4294967294 < input ) ); return input; } 以上をコンパイルし、実行すると不具合が起こります。 (1)範囲外の数値を入力しても繰り返しされない。 (2)文字を入力しても繰り返しされない。 どうすれば改善されますか? 定義域一杯に使うのはよくないことなのでしょうか? 環境はWindows XP、Borland C++ 5.5.1です。 あと、このコンパイラではlong long型は使用できないのでしょうか? よろしくお願いします。
- みんなの回答 (8)
- 専門家の回答
質問者が選んだベストアンサー
No.5 です。 scanf() で、いきなり変数に代入する限りはちゃんとしたエラーチェックは難しいですね。scanf() はそういう面であまり評判よくないです。 ちゃんとエラーチェックをしようと思えば、 ・fgets() などで、まず、文字列として読み込む。 ・読み込んだ文字列を検証する。桁数・数値以外の文字の有無など ・文字列の状態で入力が正当だと確認できれば、数値への変換関数で(または自前で)数値に変換する という手順を踏むのがよく使われる方法です。 ちなみに、文字列の読み込みは、fgets() を使うと、入力が長くても、変数の領域が破壊されないように、入力の制限ができるので、比較的安全です。 ただ、実際に、入力制限以上の文字が入力されると、それはそれで、結構面倒なことが発生しますが。
その他の回答 (7)
- beefisdead
- ベストアンサー率63% (92/145)
#1です。 scanfの返り値がマッチさせた要素の個数だそうなので、それを見れば入力ミスにも反応させられると思いますよ。 /************************************/ int rd_ok = 0; /* 適当に宣言 */ do{ printf("入力(1~4294967294 exit=0>"); rd_ok = scanf("%lu",&input); if(input==0) exit(0); while (getchar() != '\n') {} }while (input < 1 || 4294967294 < input || 1 != rd_ok); /************************************/ このままだとscanfの返り値を見る前にinputを見ているので安全ではないんですが、使い方の例と言うことでご容赦ください。 まあ、でも、scanfを使わない方がよいということに関しては#7さんに同意です。
お礼
scanfを使わない方法も勉強してみます。 ありがとうございました。
- beefisdead
- ベストアンサー率63% (92/145)
#1です。 >このままでは出来ないってことになりますね。 私の方で若干勘違いしていました。(long longに値を入れて判定するものと思っていました)すみません、この部分は取り消します。 #4・5さんがおっしゃるように、(4bit幅の)long なら 0x ffff ffff が-1と判断されるのに対して、unsigned long では 0x ffff ffff は 4294967295 になります。 比較演算子も unsigned int で比較するので、4294967294 < input の方の条件が成立するわけですね。 long longで試したときの、文字列に対して反応したのは恐らく「偶然」だと思います。 入力が想定外だったとき、scanf()は失敗したところ以降には何もしないと定められているようです。 ということは、文字列を入力するとinputは初期化されない状態で見られることになります。 初期化されない状態だと何が入っているか分かったものではないのですが、long long型であるinputの上位32bitのうち一つでも1になっていれば 0x 0000 0000 ffff ffff = 4294967295 よりも大きいことになるので、 4294967294 < input の条件が成立したものと思われます。 あとデバッグモードで試すのとリリースモードで試すのとで別の動きをすることもありますね。 デバッグ時はエラーを発見しやすいようにとメモリの初期化されていない部分を0xdeadbeefなどで埋めることがあります。 deadbeefだと、どのbyteも先頭ビットがオンになっているので、異常な状態にあることを発見しやすい、と言うことだそうです。 そう言う意味では、定義域をフルに使おうとするのは良くないってことになりますね。
補足
やはり、入力ミスに気をつけるしかないのでしょうか。
- 麻野 なぎ(@AsanoNagi)
- ベストアンサー率45% (763/1670)
No.4 です。 おそらく、整数型における数値の内部表現や、符号の処理などの理解が必要なのだろうと思います。 さて、unsinged long でも、unsigned long long でも、「符号なし」なので、マイナスの数値は絶対に表現できません。 無理にマイナスの数値を代入しようとすると、多くの処理系では、「2の補数表現」というものを用いて、先頭符号が1になった形の数値になります。 32bit 幅の -1 は、0xffffffff です。 これを unsigned long として解釈すると、4294967295 になります。 し、64bit 幅の -1 は、0xffffffffffffffff です。 これを unsigned long long として解釈すると、非常に大きな数値になります。(具体的には 4294967296 * 4294967296 - 1) つまり、-1 を入力して、 while ( ( input < 1 )||( 4294967295 < input) ); が成立しないのは、input が (おそらく 64bit 幅なので) 4294967295 より、遙かに大きな数字になっているからに過ぎません。 決して、 input < 1 が成り立っているわけではありませ。 実際に、 while(input < 1) で、-1 を入力したときに、この条件が成立しないことが確認できると思います。 input が -1 と認識されているのであれば、この条件だけで、ループを抜けるはずですから。
補足
ということは、「符号なし」の場合は入力ミスに気をつけるしかないということでしょうか。
- 麻野 なぎ(@AsanoNagi)
- ベストアンサー率45% (763/1670)
こういう質問をされるときには、単に、「範囲外の数値を入れると」ではなく、たとえば、どういう数値を使ったのかを書かれるとわかりやすいとは思います。 お使いの処理系では、unsigned long は、32bit 幅なので、0~4294967295 の範囲の数値しか表現することができません。 つまり、上記のプログラムでは、 4294967295 を入力すると、ループせずに抜ける 0 を入力すると、exit(0) でプログラム自体が終了する となります。 また、-1 だと、これは、unsigned long では、(内部表現が、0xffffffff なので)4294967295 になり、ループせずに抜けることになります。 また、scanf() の実装によりますが、数値でないものを入力すると、直前に入力した値を返すものがあります。 また、input に -1 を返したとすると、それは前述の通り、4294967295 と解釈されます。 こういったことでも、納得できない動きになっているのでしょうか?
補足
そういった動きになっているとは知りませんでした。 別のコンパイラでunsigned long long型にしてみたところ、文字入力を除外する動きも含めて期待通りの動作をしました。 unsigned long long input(void){ unsigned long long input; do{ printf("自然数を入力(1~4294967295 exit=0)>"); scanf_s("%llu",&input); if(input==0){ exit(0);} while (getchar() != '\n') {} }while ( ( input < 1 )||( 4294967295 < input) ); return input; } これとの違いは一体何なのでしょうか。コンパイラによっても動作は変わるのですか?
- jacta
- ベストアンサー率26% (845/3158)
Borland C++ Compiler 5.5.1は、今となってはかなり古くなってしまいました。 Turbo C++ Explorerで試してみると、コンパイルと実行はできました。正確な仕様が分からないので、動作が正しいのかどうかまではわかりません。 これを機会に、Turbo C++ Explorerに乗り換えるというのはどうでしょうか? Turbo C++ Explorer(に付属のBorland C++ Compiler 5.82)なら、long long型も使えることですし。
お礼
インストールしてあったVisual Studio 2008 コマンドプロンプト上でコンパイルすることにしました。
- sakusaker7
- ベストアンサー率62% (800/1280)
Borland C++ には long long という型はありません(使えません)。 64ビット幅の整数型ということなら __int64 というものがあります。
お礼
__int64型というものもあるんですか。 今度使ってみます。
- beefisdead
- ベストアンサー率63% (92/145)
Borland C++の仕様は知らないですが、とりあえず sizeof(int), sizeof(long int), sizeof(long long int) の結果を確かめてみたらいかがでしょう。 int,longが4で、long longが 8 となるようだったら、このままでは出来ないってことになりますね。 デバッグについてですが、とりあえずinputがどんな数値になっているかを確かめることは大事ですよ。 大きすぎる値を見たときscanfはinputに-1を入れてくれるようなので、そこで条件判断すればよいと思います。 文字の入力に反応させたいなら、scanfの戻り値を見ることです。一致が失敗した場合はEOFを返すことになっています。その辺のことはmanpageなど参照のこと。 http://www.linux.or.jp/JM/html/LDP_man-pages/man3/scanf.3.html そもそもscanfではなくてstrtolとかを使ったほうが健康的だと思います。
補足
>このままでは出来ないってことになりますね。 これはどういうことでしょうか。 scanfが負の値を返したとき、input < 1 は成立しないのでしょうか。
お礼
scanf以外を使うということは頭にありませんでした。 初心者向けの本にはそういった関数は載っていなくて・・・。 とても参考になりました。ありがとうございました。