ファイルの失敗に備える

初回起動でいきなり落ちる

前回作った読み込み処理には、まだ大きな穴があります。成績管理CLI を初めて起動したとき、scores.txt はまだ存在しません。存在しないファイルを "r" で開こうとすると、fopen は失敗します。

このとき fopenNULL を返します。NULL は「どこも指していない」ことを表す特別なポインタの値でした。第7章で出てきた「住所を入れる変数」に、有効な住所が入らなかった状態です。

問題は、その NULL を確かめずに fscanf(fp, ...) を呼んでしまうことです。存在しない場所を読みに行くので、プログラムはその場で異常終了します。コンパイルは通るので、実際に動かすまで気づけません。

開いた直後に必ず確かめる

対策はいたって単純で、開いた直後に NULL かどうか見るだけです。

fp = fopen("scores.txt", "r"); if (fp == NULL) { printf("保存データがありません\n"); } else { /* ここで読む */ fclose(fp); }

大事なのは、NULL は必ずしも異常ではないという点です。初回起動でファイルが無いのは当たり前のことで、そのときは「0 人からはじめる」と決めておけばよいだけです。エラーで止めるのか、無かったことにして進めるのかは、プログラムを書く側が決めます。

失敗が起こる理由はほかにもあります。読もうとしたものがディレクトリだった、権限が無くて読めない、書き込み先のディスクがいっぱい、といったものです。"w" で開くときも失敗しうるので、書き込み側も同じように確かめます。

閉じるのは開けたときだけ

もうひとつ気をつける点があります。fcloseNULL を渡してはいけません。開けなかったのに閉じようとするのは、持っていない鍵で鍵をかけるようなものです。上の例で fcloseelse の中に置いているのはそのためです。

また、return 1; で終わらせる形もよく使われます。main が返す 0 以外の値は「異常終了した」という合図で、あとで他のプログラムと組み合わせるときに効いてきます。読み込みに失敗しただけなら続けてよくても、保存に失敗したまま「保存しました」と表示するのは嘘になるので、書き込み側は止めるほうが親切です。

等号を1つにしない

if (fp == NULL)== を 1 つにして if (fp = NULL) と書くと、比較ではなく代入になります。fpNULL で上書きされたうえ、条件は必ず偽になるので、チェックが素通りしたまま次の行で異常終了します。第3章でも出てきた間違いですが、ポインタが相手だと被害が大きくなります。

今回は、確実に存在しない missing.txt を開いて NULL が返るのを確かめてから、scores.txt に書いて読み直すところまで通します。

要件

  1. missing.txt を読み込みモードで開き、NULL なら「missing.txt は開けませんでした」と表示する
  2. scores.txt を書き込みモードで開き、NULL なら「保存に失敗しました」と表示して return 1 で終わる
  3. scores.txt を読み込みモードで開き、NULL なら「読み込みに失敗しました」と表示して return 1 で終わる
  4. 読み直した内容を「田中さんの82点を保存しました」の形で表示する

入出力例

main("田中 82") → "missing.txt は開けませんでした 田中さんの82点を保存しました" main("佐藤", "90") → "missing.txt は開けませんでした 佐藤さんの90点を保存しました"

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.c
学習モード

メモ

ファイルの失敗に備える

⌘S で保存