Scanfの問題点とその回避法

(under construction; 更新日時:)

標準入出力

これまでの課題プログラムなどで、文字(テキスト)の標準入力からの読み込み、 標準出力への書き出しについては、一応理解していることと思います。 (標準入出力とパイプについて復習が必要な人は ここを参照)。

数値データの読み込み法; scanfの問題点

数値データをキーボードから入力する際、手軽なのでscanfを使うこともある と思いますが、指定した書式と入力書式が正しく一致していないと正しく動作 せず、暴走するようなケースも生じます。

例えば、2項目か3項目から成る数値や文字データを1行で読みたい場合、入力 データがプログラムと整合していなければなりませんが、入力するのが人間だ としたら、ちょっとしたキーの打ち間違いや項目を飛ばすといったことはしょっ ちゅうあります。極端な話、そのたびにプログラムが暴走するようではたまり ません。

このプログラムは、scanfにおけるそのような問題点を確認するためのも のです。scanfにより、キーボードから入力された「月、日、メモ、体重、体 脂肪率」を読み込み、それを単に打ち出すだけのプログラムです。このソース のコメントに添付したデータ以外も、いろいろな書式で試してみてください。 プログラムが、何か文字をプリントし続けて止まらない(暴走する)ときは、 慌てずC-c(コントロールキーを押しながら、cキーを押す)で止めましょう。

fgets+sscanfによる解決法

上述の問題点を回避するには、「(キーボードからの)入力文字を読み込み、そ れを数値に変換する」という二段階処理が必要になります。その際、安全のた め、読み込み可能な最大文字数をプログラム側で指定することができればなお よい。fgetsではこれが可能です。

上の例(体脂肪プログラム?)を、この方法で書き直すと このようになります。 scanfのときと同じテストデータで試してみてください。不正入力のときの動 作はどうなるでしょうか。

その骨組みは、次の通りです。

  char buf[BUFSIZE]; /* fgetsで読んだ文字を一旦格納する場所  */
  int month, day;   double weight, fat;  char note[128];
  .....
  fgets(buf, sizeof buf, stdin); /* 標準入力からbufへ読み込む  */
  sscanf(buf, "%2d/%2d %s %lf %lf", 
               &month, &day, note, &weight, &fat);
                                 /* bufの文字を書式に従って変換 */
sscanfは、変換に成功した数を返す(この場合は5)ので、これをチェックする ことができます。このプログラムでは、入力ミスがあれば何もエコーバックせ ず、ただ次のデータ入力を待ちますが、既定値を使う、ミスを指摘する、など これ以外の処理も可能なことは容易に想像できると思います。そこから先は、 入出力インターフェースをどのように設計するかという問題になる訳です。

このような「(キーボードからの)入力文字を一時バッファに読み込み、それを 数値その他に変換する」という二段階処理は、実はこれまでにも使ってきまし た。

  1. fgets で一時バッファに読み込み、 sscanf で変換する。 (ここで示した例)
  2. fgets で一時バッファに読み込み、atoi などで変換する。 (だいぶ前の課題で示した例)
  3. 自作の getline などで一時バッファに読み込み、atoi や sscanf で変換 する。(教科書)
プログラムが実際に外界と接する部分(インターフェース)の設計には細心の注 意を払わねばなりません。(この講義や単位取得という狭い意味でなく)今後の ために、1や2の標準的な手順を整理しておくことを勧めます。

fscanfなら安全か?...scanf, fscanf, sscanf

scanfは、書式不備なデータに対して極めて不安定なことを説明しました。そ の兄弟に、fscanfがあります。scanfが標準入力を対象とするのに対し、 fscanfは入力の対象とするファイル(ストリーム)を指定することができます。 この点を除けば、fscanfは全くscanfと同じです。つまり、fscanfにしたから 安全とは言えません。

これに対して、上で見たsscanfは、(メモリー内の)文字列を入力対象としてい る点で、大きく異なります。つまり、一旦バッファに読み込んだ長い文字列 (たぶん、数値、文字以外に、空白やカンマや改行などを含むかもしれない) を、指定書式に従って変換するためのものです。 それで、文字列を丸のみするための関数 fgets と組み合わせて使う訳です。

fgetsとgets

では、fgetsとgetsを比較してみましょう。結論から言えば、上の scanf/fscanfの関係と異なり、これら両者には大きな違いがあります。この違 いは重要で、マニュアル(man fgetsなど)にもあるように、getsを使うのでな く、必ずfgetsを使うべきです。

そうした訳で、データの入力には、fgets+sscanfを組み合わせるのが安全です。 (sscanfは書式変換のためですから、データ型によってはatoiなど他の手段を 使ってもよい)。

[自習] 自分の計算機環境で、fgets, atoi, fscanf, fprintf について調べなさい。

発展課題へのいくつかの解--ファイルアクセス法のヒントを兼ねて

標準入力からいくつかの書式の整数データを受け入れる方法

以下に示すのは、整数データが、計算機内部でどのようなビットイメージで表 現されているか確認するプログラムである。最初の版は、単にテストデータを プログラム本体に埋め込んで確認するものであるが、それを標準入力からのデー タ受け取りに変え、さらに16進標記の整数も受け入れるように変えている。

画像データのバイナリ出力法

課題1で、画像ファイル(pgmフォーマット)を作 る簡単な例を示した。その例では、各画素値がアスキーデータで表されて おり、そのまま標準出力へ出すようになっていた。

この書式は、簡単に画像を作れる点で便利であるが、少し複雑なイメージを作 るには不便である。このためには、プログラムで確保したメモリにイメージデー タを描き、それをバイナリ出力する必要がある。このプロ グラムはそのような例である。これによって作られるファイルは、xvなど のビューアで見ることができる。

目次に戻る


maekawa@cda.ics.saitama-u.ac.jp

© Copyright 2001 Hitoshi Maekawa. All right reserved.


Up to Programming Lang. home. Up to lectures-etc.