12章 stdioライブラリー



12.1:
なぜ以下のコードはうまく動かないのか。

char c;
while((c = getchar()) != EOF) ...

A:

getchar()の戻り値を格納する変数はintでなければならない。 getchar()は、文字型のあらゆる値を返すだけでなくEOFも返す。 getchar()の戻り値をcharで渡すと、普通の文字が返ってきたのにEOF と誤解されたり、EOFが他の値に変えられて(特にデータ型charが符号 なしの場合)、いつになってもEOFが出てこないかもしれない。

References:
K&R1 Sec. 1.5 p. 14; K&R2 Sec. 1.5.1 p. 16; ANSI Sec. 3.1.2.5, Sec. 4.9.1, Sec. 4.9.7.5; ISO Sec. 6.1.2.5, Sec. 7.9.1, Sec. 7.9.7.5; H&S Sec. 5.1.3 p. 116, Sec. 15.1, Sec. 15.6; CT&P Sec. 5.1 p. 70; PCS Sec. 11 p. 157.


12.2:
なぜ以下のコードは最後の行を2回コピーするのか。

while(!feof(infp)) {
fgets(buf, MAXLINE, infp);
fputs(buf, outfp);
}

A:

Cでは、EOFは入力ルーチンが読もうとしてファイルの終わり (End-Of-File)にたどり着いた後であることを示しているだけである (言い換えればC言語のI/OはPascalのI/Oとは異なる)。たいていは入 力関数(この場合はfgets)の戻り値をチェックすればよい。feof()を 使う必要がまったくない場合が多い。

References:
K&R2 Sec. 7.6 p. 164; ANSI Sec. 4.9.3, Sec. 4.9.7.1, Sec. 4.9.10.2; ISO Sec. 7.9.3, Sec. 7.9.7.1, Sec. 7.9.10.2; H&S Sec. 15.14 p. 382.


12.4:
私のプログラムのプロンプトと中間出力が、画面上にあらわれないこ とがある。特に、パイプを通して出力を他のプログラムに渡したとき に、この問題が起こる。

A:

出力が見えて欲しいところでは、必ず明示的にfflush(stdout)を使っ て出力をはきだすこと。ほっておいてもいくつかの仕組みが、 fflush()を"適切なときに"実行してくれる。しかし、これはstdoutが 端末のときにしか適応されないことが多い(質問12.24も参考のこと)。

References:
ANSI Sec. 4.9.5.2; ISO Sec. 7.9.5.2.


12.5:
RETURNキーが押されるのを待つことなく、一度に一文字ずつ読むこと ができるか。

A:

質問19.1を参照のこと。


12.6:
どうすれば、printfのフォーマット文字列を使って'%'を出力できる のか。\%を試したけれど、うまくいかなかった。

A:

%%というふうに、%を二回続けて書け。

\%ではうまくいかない。バックスラッシュ\はコンパイラーのエスケー プシーケンスで、printfのエスケープシーケンスは%だ。

質問19.17も参照のこと。

References:
K&R1 Sec. 7.3 p. 147; K&R2 Sec. 7.2 p. 154; ANSI Sec. 4.9.6.1; ISO Sec. 7.9.6.1.


12.9:
%lfをprintf()で使うのは間違っていると教えてくれた人がいた。 scanf()が%lfを使うのに、どうしてprintf()は%fを使うのか。

A:

printf()の%f指定子はfloatの引数にもdoubleの引数にも作用する。" 省略時の引数の格上げ"により(この規則はprintfのような可変数個の 引数を取る関数に、その関数がプロトタイプを持っていてもいなくて も適応される)、データ型floatの変数はdoubleに格上げされる。よっ てprintf()が見るのはdoubleの変数だけである。質問12.13, 15.2も 参照のこと。

References:
K&R1 Sec. 7.3 pp. 145-47, Sec. 7.4 pp. 147-50; K&R2 Sec. 7.2 pp. 153-44, Sec. 7.4 pp. 157-59; ANSI Sec. 4.9.6.1, Sec. 4.9.6.2; ISO Sec. 7.9.6.1, Sec. 7.9.6.2; H&S Sec. 15.8 pp. 357-64, Sec. 15.11 pp. 366-78; CT&P Sec. A.1 pp. 121-33.


12.10:
printf()で欄の可変の幅を実装するのはどうすればいいのか。つまり、 %8dと書く替わりに、実行時に幅を指定したい。

A:

printf("%*d", width, n)が君の望むことをやってくれる。質問12.15 も参照のこと。

References:
K&R1 Sec. 7.3; K&R2 Sec. 7.2; ANSI Sec. 4.9.6.1; ISO Sec. 7.9.6.1; H&S Sec. 15.11.6; CT&P Sec. A.1.


12.11:
千単位でコンマで切って数を表示するのはどうすればよいか。通貨に 合わせた書式で表示するのは。

A:

<locale.h>にあるルーチンでこれらの操作に対応するものも出始めた。 しかし上のどちらの仕事についても標準のルーチンというのは存在し ない。(printf()が、その土地の慣習にしたがうことといえば小数点 の表示に使う文字を変更することだけである。)

References:
ANSI Sec. 4.4; ISO Sec. 7.4; H&S Sec. 11.6 pp. 301-4.


12.12:
なぜコードscanf("%d", i);がうまく動かないのか。

A:

scanf()は、値を書き込む変数へのポインターを必要とする。 scanf("%d", &i);と書かなければならない。


12.13:
なぜ以下のコードは動かないのか。

double d;
scanf("%f", &d);

A:

printf()と違ってscanf()ではdoubleには%lfを、floatには%fを使う。 質問12.9も参考のこと。


12.15:
scanf()の書式文字列に可変の幅を指定するのはどうやればよいか。

A:

できない。scanf()の書式文字列にアスタリスクを書くと、代入を抑 制することとなる。ANSIの文字列作成(stringizing)や文字列連結の 機能を使って、ほとんど同じことができる。scanf()の書式文字列を その場で作るのもいい。


12.17:
scanf()を使ってキーボードから読み取ると、もう一行余計に打ち込 むまで、ハングするようだ。

A:

知らなくて驚くだろうけど、scanfの書式文字列で\nは改行を意味す るのではなく、空白が続くかぎり読んでは捨てることを意味している。 質問12.20も参照のこと。

References:
K&R2 Sec. B1.3 pp. 245-6; ANSI Sec. 4.9.6.2; ISO Sec. 7.9.6.2; H&S Sec. 15.8 pp. 357-64.


12.18:
scanfの%dを使って数字を読んで、文字列をgets()で読もうとしてい る。けれどコンパイラーはgets()の呼び出しを飛ばしているようだ。

A:

scanfの%dはおしりの改行は食べてくれない。もし入力の数のすぐ後 ろに改行が来たら、その改行はgets()の入力としての条件を満足する。

一般に、scanf()の呼び出しと、gets()(その他の入力ルーチンも)の 呼び出しを混ぜて使ってはいけない。scanf()の改行の扱いが変わっ ているので必ずやっかいなことになる。scanf()ですべて読み込むか、 scanf()を使わないかのどちらかである。

質問12.20, 12.23も参照のこと。

References:
ANSI Sec. 4.9.6.2; ISO Sec. 7.9.6.2; H&S Sec. 15.8 pp. 357-64.


12.19:
scanf()を使うのときに、こっちの望んだ数値をユーザーが入力した ことを確かめるのに、scanf()の戻り値を調べるほうが安全だと考え た。けれど、たまに無限ループに入ってしまうようだ。

A:

scanf()が数字を変換しようとするときには、数字でない文字に出く わすと変換を終了し、その上それらの文字を入力ストリームに放置す る。したがって他の手段を取らないと、数字でない入力が思いがけな いところで出てくるとscanf()が何度も"ジャム"ってしまう。scanf() は問題となる文字を通り過ぎて、その後ろにある正しいデータに行く ことはない。もしユーザーがscanf()の数値のフォーマットである%d や%fなどに対して'x'のような文字を入力したとすると、単にプロン プトをまた出して同じscanf()を呼び出すようなコードは、即座に同 じ'x'に出くわすことになる。

質問12.20も参照のこと。

References:
ANSI Sec. 4.9.6.2; ISO Sec. 7.9.6.2; H&S Sec. 15.8 pp. 357-64.


12.20:
どうして誰もがscanf()を使わないほうがいいというのか。かわりに 何を使えばいいのか。

A:

scanf()には数多くの問題がある。質問12.17, 12.18, 12.19を参照の こと。さらにscanf()の%sフォーマットはgets()が抱えているのと(質 問12.23を参照)同じ問題を抱えている。つまり受け取りに使うバッファ があふれる心配がないと保証するのは難しい。

もっと一般的な話として、scanf()はかなりしっかりした構造を持っ た、書式にのっとった入力を対象に設計されている(実際scanfという 名前は「scan formatted(書式を持ったものを走査する)というところ から来ている」)。注意していれば、処理が成功したかどうかわかる。 しかし、どこで失敗したかは大体のところしか分からないし、どうい う風にとか何故かはぜんぜんわからない。scanf()を使った場合エラー からのまっとうな復帰はほとんど不可能である。たいていは1行丸々 (fgets()かなにかで)読み込んで、sscanf()かその他の技を使って解 釈するほうがずっと簡単である。(strtol()とかstrtok()とかatoi() といったルーチンも役に立つことが多い。質問13.6も参照のこと。) どうしてもsscanf()を使わなければならなければ、戻り値を調べてこ ちらが期待した数だけ項目が見つかったことを確認するのを忘れては いけない。

References:
K&R2 Sec. 7.4 p. 159.


12.21:
sprinftの呼び出しで、行き先のバッファがどれくらいの大きさが必 要かどうすればわかるか。sprintfの呼び出しで行き先のバッファを、 どうすればあふれさせずにすむか。

A:

この2つの鋭い質問には立派な解答は(まだ)ない。ないということは、 これまでのstdioライブラリーの最大の欠陥をたぶん表わしている。

書式文字列が既知で結構単純なときは、毎回特別な方法でバッファの 大きさを予想することができる。書式が一つか二つの%sからできてる ときは、固定の文字の部分は自分で数えて(またはかわりにsizeofに 数えさせて)、挿入される文字列の分はstrlen()を呼んで数えさせ、 結果を加える。%dがどれくらいの大きさを占めるかは、以下のような コードで安全のため大きめに見積もることができる。

#include <limits.h>
char buf[(sizeof(int) * CHAR_BIT + 2) / 3 + 1 + 1];
sprintf(buf, "%d", n);

(上のコードは8進数表現の数を表わすのに必要な文字の数を計算する ものである。10進数表現なら必ず同じかより小さい領域で済む。)

書式文字列がもっと複雑だったり実行時までわからない場合は、どれ くらい大きなバッファが必要か予想することはsprintf()を改めて実 装するのと同じくらい困難となり、それだけ間違いを犯しやすくなる (したがって勧めない)。最後の手段として時々提案されるのは fprintf()を使って同じテキストをビットバケツや一時ファイルに書 き込んで、fprintf()の戻り値やファイルの大きさを見ることである (ただし質問19.12も参照のこと)。

バッファがそんなに大きくないかもしれない場合は、バッファがあふ れないとかメモリの別の部分を上書きしないと保証されない限り、 sprintf()を呼びたくないだろう。いくつかのstdio(GNUや4.4bsdの) は名前から機能がわかるsnprintf()関数を用意している。これは以下 のように使う。

snprintf(buf, bufsize, "You typed \"%s\"", answer);

ANSI/ISO Cの将来の改訂がこの機能を取り入れることを願うばかりで ある。

References:
Rationale Sec. 4.9.7.2; H&S Sec. 15.7 p. 356.


12.23:
なぜgets()を使うなと誰もが言うのか。

A:

getsでは、これから読もうとしているバッファの大きさを指定できな いので、バッファがあふれてしまうことを防ぐことができない。質問 7.1の、gets()の代わりにfgets()を使う方法を説明したコードを参考 のこと。


12.24:
なぜprintf()を呼ぶと変数errnoにENOTTYが設定されるのか。

A:

stdioパッケージの多くの実装はstdoutの先が端末のときは振る舞い を少し変える。こういう実装では、出力先を判断するためにstdoutの 先が端末でないときは必ず失敗する操作(これがENOTTYと設定する)を おこなう。よって出力の操作が完全に成功するにもかかわらずerrno の値はENOTTYとなる。(プログラムがerrnoの中身を調べることは、エ ラーが報告されたときだけ意味があることに注意。)

References:
ANSI Sec. 4.1.3, Sec. 4.9.10.3; ISO Sec. 7.1.4, Sec. 7.9.10.3; CT&P Sec. 5.4 p. 73; PCS Sec. 14 p. 254.


12.25:
fgetpos/fsetposとftell/fseekの違いは。fgetpos()やfsetpos()は何 の役に立つのか。

A:

fgetpos()とfsetpos()は特別なtypedefであるfpos_tを、ファイル内 でのオフセット(位置)を表わすのに使う。このtypedefで隠したデー タ型を適切に選べば、オフセットの大きさを好きなように選ぶことが できる。したがってfgetpos()とfsetpos()でどんなに巨大なファイル でも扱える。一方ftell()とfseek()はオフセットを表わすのにlong intを使う。したがってlong intで表わすことのできるオフセットに 限定されてしまう。

References:
K&R2 Sec. B1.6 p. 248; ANSI Sec. 4.9.1, Secs. 4.9.9.1,4.9.9.3; ISO Sec. 7.9.1, Secs. 7.9.9.1,7.9.9.3; H&S Sec. 15.5 p. 252.


12.26:
ユーザーが先打ちした入力が、次に入力待ちするところで読まれるこ とがないように、処理されていない入力をはきだすことができるか。 fflush(stdin)は効くか。

A:

fflush()は、出力ストリームにしか定義されていない。fflushの "flush(押し流す)"は、バッファリングされた文字の(捨てるのではな く)書き込みを達成することを意味している。読まれてない入力を捨 てることは、入力ストリームをfflushするということと同じ意味では ない。stdioの入力バッファの読まれていない入力を捨てる一般的な 方法はないし、そんなことをするだけでは充分でない。読まれていな い文字は、O/Sレベルのバッファにもため込まれている可能性がある。


12.30:
fopenのモード「r+」を使ってファイルを開いて、次にある文字列を 読んで、最後に修正した文字列を書き戻すことでファイルを更新しよ うとしている。でもうまくいかない。

A:

書く前にfseek()を必ず呼ぶこと。上書きしようとしている文字列の 先頭にシークして戻すことが必要であるし、読み書き両方可能の「+」 モードでデータを読むときと書くときの間にはfseek()かfflush()が 必要である。元々あった文字と同じ個数しか上書きできないことも忘 れてはいけない。質問19.14も参照のこと。

References:
ANSI Sec. 4.9.5.3; ISO Sec. 7.9.5.3.


12.33:
プログラムの中からstdinやstdoutの先をファイルに変更するのはど うすればよいか。

A:

freopen()を使え(ただし以下の質問12.34を参照のこと)。

References:
ANSI Sec. 4.9.5.4; ISO Sec. 7.9.5.4; H&S Sec. 15.2.


12.34:
freopen()を使った後で、元のstdout(あるいはstdin)に戻すことがで きるか。

A:

よい方法は存在しない。もし行ったり来たりしたいのであれば、最高 の方法はfreopen()なんて使わないことである。自分で明示的に定義 した出力(あるいは入力)ストリーム変数を使うこと。そうすれば思う ままに出力(または入力)を割り当てて、しかも元のstdout(あるいは stdin)を壊さなくてすむ。


12.38:
バイナリーデータのファイルを、ちゃんと読むにはどうすればよいか。 ときどき0x0aとかx0dといった値が壊れてしまうのを目撃する。デー タに0x1aが含まれていると本来より早くEOFに出くわすようだ。

A:

バイナリーデータを読み込むときには、fopen()を呼ぶ際に、テキス トファイルの変換が実行されないよう、「rb」モードを指定すること が必要である。同じように、バイナリーファイルを書き込むときは、 「wb」を使わなければならない。

テキストとバイナリーはファイルをオープンするときに区別される。 いったんファイルを開いたら、ファイルにどちらのI/Oコールをして いるかは関係ない。質問20.5も参照のこと。

References:
ANSI Sec. 4.9.5.3; ISO Sec. 7.9.5.3; H&S Sec. 15.2.1 p. 348.

目次へ戻る