エラーの調べ方
失敗したことは分かる。理由が分からない
fopen が NULL を返したとき、分かるのは「開けなかった」ことだけです。ファイルが無いのか、名前を打ち間違えたのか、読む権限が無いのか、開きすぎて上限に達したのか。これを区別できないと、利用者に出せるのは「開けませんでした」という役に立たない一言だけになります。
C は戻り値を1つしか返せないので、失敗の理由は別の場所に置かれます。それが errno です。
errno は「直前の失敗の理由」が入る場所
errno.h を include すると errno が使えます。標準ライブラリの関数は、失敗したときにここへ理由を表す番号を入れます。番号には名前が付いていて、よく出会うのは次のものです。
ENOENTそんなファイルやディレクトリは無いEACCES権限が無いEEXISTすでにあるENOMEMメモリが足りない
malloc が NULL を返したときも ENOMEM が入ります。
扱いで大事なきまりが2つあります。1つめは、成功したときに errno がゼロに戻る保証は無い ことです。前の失敗の値が残っていることがあるので、errno を見てよいのは「戻り値で失敗を確かめたあと」だけです。2つめは、確かめたい呼び出しの直前に errno = 0; と自分で消しておくことです。間に別の関数を挟むと、そちらの失敗で上書きされます。
errno = 0;
FILE *fp = fopen(name, "r");
if (fp == NULL) {
if (errno == ENOENT) {
printf("ファイルが見つかりません\n");
}
}perror と strerror
番号のまま出しても人には読めません。文にしてくれる道具が2つあります。
perror("fopen"); /* fopen: No such file or directory */
printf("%s\n", strerror(errno)); /* No such file or directory */perror は渡した文字列と理由をつないで 標準エラー出力 に出します。標準出力とは別の流れなので、画面には出ても、パイプでつないだ先には流れません。この教材の採点は標準出力だけを見るため、perror で書いたものは答え合わせに使えません。判定に出したい文は printf で出し、perror は開発中の手がかりとして使い分けます。
strerror は理由の文を文字列で返すので、こちらは printf に混ぜられます。ただし返ってくる文は英語で、環境によって言い回しが変わります。利用者に見せる文を自分の言葉で書きたいときは、このレッスンのように errno の値で分岐します。
では、開けなかった理由を具体的に伝えるプログラムを書いてみましょう。
要件
- 開けたときは scores.txtを開きました と 点数は80点です の2行を出す
- 開けなかったときは missing.txtを開けませんでした と 理由はファイルが見つからないことです の2行を出す
- 理由の判定には errno を使い、ENOENT のときだけ ファイルが見つからない と言い切る
- fopen を呼ぶ直前に errno を 0 にしておく
入出力例
main("scores.txt
") → "scores.txtを開きました
点数は80点です"
main("missing.txt
") → "missing.txtを開けませんでした
理由はファイルが見つからないことです"