標準入力 scanfの使用上注意点
〜バグの魔界scanf
------------------------------------summery-----------------------------------------------------
scanf in C program is a hotbed of bugs. Reading strings into arrays, you
should be careful.
When you use "scanf", please note that whitespace is recognized as a delimiter and
newline characters remain in the buffer.
とにもかくにも、C言語の初心者にはscanfは厄介なバグの温床になります。scanfを使うときの
注意点をいくつかまとめてみました。
-------------------------------------------------------------------------------------------------
バグの魔界scanfの森へようこそ。scanfはバグの温床。
バグの魔界という副題をつけたほどに、このscanfというやつにはわけのわからないバグ(仕様?)があり
ます。うかつに使うとバグの魔界に取り込まれてしまいます。
scanfの基本的な使い方は、画面に出力(printf)・キーボードから入力(scanf)・変数 に書きましたので
そちらをご参照下さい。
ちなみに、標準入力とはPCの場合は一般的にはキーボードになります。つまりキーボードからの入力ということ。
基本的な使い方は知ってるさという方々、では、魔界へさっそく旅立つとしましょう。
※(ご参考)標準入力からの値の入力に関するショートプログラムは以下のページにあります。
・値の入力
・標準入力の小技〜分割して入力
まずは
Hello
my
World
と3回に分けて入力してみます。
これは期待通りの動き(出力)ですね。上出来です。
でも、実は手放しでは喜べません。一抹の不安が胸をよぎります。
何が不安かって?
だって、char s1[5]と言う配列の宣言は、s1[0],s1[1],s1[2],s1[3],s1[4]が用意されるわけですよね。
で、C言語の文字列は最後にヌル文字(\0)をつける約束ですよね。ヌル文字が1文字文の場所を
とるので、s1[5]の宣言では、実際の文字は4文字までしか格納できないはず・・
なのに、Hello と5文字入ってる。Worldもそうだ。
なんか怪しいくないですか。いやな予感がしますよね。
えっ、そうなの?という人。まだCの超初心者。
怪しいぜ、という人はCの初心者
Cはそういうもんさ、と言う人は中級以上ですね。
次は、
Hello my World
と1行で入力してみます。
/Hello/
/my/
/World/
と1行が空白を区切りにして3つに分かれました。
scanfはデフォルトでは、空白やTab、改行を区切りとして入力するので、このような動作になります。
英語の場合(印欧語の場合は押しなべてそうですが)、空白を区切りとして認識するのは合理的な仕様と
言えますよね。
まあ、一応ここも仕様通りの正常な動きをしているように見えます。まだ魔界の入口なのでバグには遭遇
していないようです。・・ホントかなあ
じゃ、次は
HelloMyWorld
て入力してみよう。
一気に12文字だぞ。
れれっ、動作がおかしい。
そのあと、good, boy, 1, 2, 3 と計6回も入力できてしまった。
いよいよ魔物が現れやがったな。って感じですか。
C言語で配列で文字列を扱う場合は要注意です。
C言語では配列の添え字チェックはされません。BASICでは配列の添え字を超えて入力しようとすると
"out of range"などというエラーを出してくれますが、Cでは平気で添え字を超えて格納できるし、読み出し
もできてしまいます。
また、文字列の終わりは\0(ヌル文字)と決まっているので、配列の添え字を超えても、\0が現れるまでを
文字列として認識します。もう、ほとんどアセンブラに近いプリミティブさですね。
こんな風に、C言語は配列で確保した領域を超えて文字列を格納できてしまうので、その場合
こんな風に動作がおかしくなることもあります。場合によっては他のデータの領域に食い込んで
データを壊す可能性もあるし、C言語の配列は取扱いに気を使います。
文字列の長さに制限を掛けている場合は、strlenなどで入力した文字列の長さをチェックするなど、
自分で入力チェック部分を実装しておく必要があります。
scanfとgetsの小技にstrlenによる文字列チェックについて書きましたので、参照して下さい。
では、まず
Hello
と入力
そうすると
/Hello/
/
/
となった。
実際は入力バッファには
Hello<\n>(=改行コード)
というデータが取り込まれており、scanfが入力バッファからデータを取りこむときは、文字列の
データだけを取り込み、改行コードはバッファに残したままになるようです。
で、2つめのscanfは1文字としてバッファから改行コードを取り込んでしまったようです。
魔界な仕様ですね。
次は
Hello my World
と入力してみます。
s2はHelloの後の空白文字を取り込んでいますね。
どうも、文字列 −> 1文字
という動作はできないようですね。それがC言語のscanfの仕様のようなのでしようがないですね。
s2に意図通りに1文字入力する方法がないかというと、ちょっとした工夫でそれも可能です。
それはscanfとgetsの小技に書きましたのでご参照下さい。
例によって、5文字をはるかに超える文字列を入力してみます。
やっぱり、動作は正常ではないですね。
まずは
Hello My World
と入力してみます.
これは意図通りに動作しています。
次に試しに、
Hello<改行>
my<改行>
World<改行>
と入力してみると、3個の文字列を入力し終わってから出力しました。
これも仕様通りの動きですね。