ファイルの失敗に備える
初回起動でいきなり落ちる
前回作った読み込み処理には、まだ大きな穴があります。成績管理CLI を初めて起動したとき、scores.txt はまだ存在しません。存在しないファイルを "r" で開こうとすると、fopen は失敗します。
このとき fopen は NULL を返します。NULL は「どこも指していない」ことを表す特別なポインタの値でした。第7章で出てきた「住所を入れる変数」に、有効な住所が入らなかった状態です。
問題は、その NULL を確かめずに fscanf(fp, ...) を呼んでしまうことです。存在しない場所を読みに行くので、プログラムはその場で異常終了します。コンパイルは通るので、実際に動かすまで気づけません。
開いた直後に必ず確かめる
対策はいたって単純で、開いた直後に NULL かどうか見るだけです。
fp = fopen("scores.txt", "r");
if (fp == NULL) {
printf("保存データがありません\n");
} else {
/* ここで読む */
fclose(fp);
}大事なのは、NULL は必ずしも異常ではないという点です。初回起動でファイルが無いのは当たり前のことで、そのときは「0 人からはじめる」と決めておけばよいだけです。エラーで止めるのか、無かったことにして進めるのかは、プログラムを書く側が決めます。
失敗が起こる理由はほかにもあります。読もうとしたものがディレクトリだった、権限が無くて読めない、書き込み先のディスクがいっぱい、といったものです。"w" で開くときも失敗しうるので、書き込み側も同じように確かめます。
閉じるのは開けたときだけ
もうひとつ気をつける点があります。fclose に NULL を渡してはいけません。開けなかったのに閉じようとするのは、持っていない鍵で鍵をかけるようなものです。上の例で fclose を else の中に置いているのはそのためです。
また、return 1; で終わらせる形もよく使われます。main が返す 0 以外の値は「異常終了した」という合図で、あとで他のプログラムと組み合わせるときに効いてきます。読み込みに失敗しただけなら続けてよくても、保存に失敗したまま「保存しました」と表示するのは嘘になるので、書き込み側は止めるほうが親切です。
等号を1つにしない
if (fp == NULL) の == を 1 つにして if (fp = NULL) と書くと、比較ではなく代入になります。fp が NULL で上書きされたうえ、条件は必ず偽になるので、チェックが素通りしたまま次の行で異常終了します。第3章でも出てきた間違いですが、ポインタが相手だと被害が大きくなります。
今回は、確実に存在しない missing.txt を開いて NULL が返るのを確かめてから、scores.txt に書いて読み直すところまで通します。
要件
- missing.txt を読み込みモードで開き、NULL なら「missing.txt は開けませんでした」と表示する
- scores.txt を書き込みモードで開き、NULL なら「保存に失敗しました」と表示して return 1 で終わる
- scores.txt を読み込みモードで開き、NULL なら「読み込みに失敗しました」と表示して return 1 で終わる
- 読み直した内容を「田中さんの82点を保存しました」の形で表示する
入出力例
main("田中 82") → "missing.txt は開けませんでした
田中さんの82点を保存しました"
main("佐藤", "90") → "missing.txt は開けませんでした
佐藤さんの90点を保存しました"