C ライブラリ関数 -feof()
feof()ファイルがすでに末尾まで読まれたかどうかを検出するために使用されます。
これを「終点探知機」と理解できます——内容を読み終えるたびに「もう終わりか?」と尋ねるようなものです。
説明
C ライブラリ関数int feof(FILE *stream)指定されたストリーム stream のファイル終了識別子をテストします。
ここで新しい概念が2つ登場するので、それぞれ説明します:
ストリーム(stream)は、C 言語における入出力の抽象化です。ハードディスク上のファイル、キーボード入力、ネットワークデータのいずれであっても、C 言語では一律に「ストリーム」として扱います。ストリームを水道管のように想像できます。データが一方からもう一方へ流れていきます。
ファイル終了識別子(EOF マーク)は、各ファイルストリーム内部に備わっている状態マークです。これは本のしおりのようなもので、ファイル内容が読み終わると自動的に「読み終わり」のページにめくられます。
feof() の役割はこのしおりを確認することです——しおりが「読み終わり」を示していれば、feof() は非ゼロ値を返します。しおりがまだ「未読了」の位置にあれば、feof() は 0 を返します。
宣言
以下は feof() 関数の宣言であり、各部分に分けて意味を解説します:
int feof(FILE *stream)
intは戻り値の型です——feof() は整数を返し、非ゼロは末尾に達したことを意味し、0 はまだ達していないことを意味します。
FILE *streamはパラメータです——ファイルポインタ(fopen() でファイルを開いたときに返されるポインタ)を渡す必要があり、feof() にどのファイルストリームを検査するかを伝えます。
パラメータ
feof() は1つのパラメータを受け取ります:
| パラメータ | 型 | 説明 |
|---|---|---|
| stream | FILE * | ファイルポインタ。fopen() 関数によって返され、検査対象の開かれたファイルを指します |
戻り値
戻り値の説明は以下のとおりです:
| 戻り値 | 意味 |
|---|---|
| 非ゼロ値 | ファイルはすでに末尾まで読まれており、読み取れる内容はもうありません |
| 0 | ファイルはまだ読み終わっておらず、読み取りを続行できます |
重要なポイント:feof() は「ファイルの末尾を読み取ろうと試みた後」にのみ非ゼロ値を返します。ちょうど最後の1文字を読み終えただけで、まだ続けて読もうと試みていない場合は、feof() は依然として 0 を返します。このタイミングの問題については次の節で詳しく説明します。
例
以下の例は feof() 関数の正しい使い方を示しています。
プログラムはファイルの内容を1文字ずつ読み取り、1文字読むたびに feof() で末尾に達したかどうかを検査します。
例
int main ()
{
// FILE * はファイルポインタ型であり、開かれたファイルを指すために使用されます
FILE *fp;
// fgetc() は char ではなく int を返します。EOF(-1)を返す必要があるかもしれないためです
int c;
// fopen() はファイルを開きます。"r" は読み取り専用モード(read)を意味します
fp = fopen("file.txt","r");
// ファイルが存在しない、または権限がない場合、fopen() は NULL を返します
if(fp == NULL)
{
// perror() はシステムエラー情報を出力し、問題の特定に役立ちます
perror("ファイルを開くときにエラーが発生しました");
return(-1);
}
// 無限ループ + 内部での判断方式を使用
// これにより「先に読み取り、後で判断」が保証され、余分な内容を読み取ることを防げます
while(1)
{
// ステップ1:ファイルから1文字読み取る
c = fgetc(fp);
// ステップ2:読み取った直後に feof() で末尾に達したかどうかを検査する
if( feof(fp) )
{
// 末尾に達したのでループを抜け、c を処理しない
break ;
}
// ステップ3:末尾でないことを確認した後、初めて c の値を使用する
printf("%c", c);
}
// fclose() はファイルを閉じ、システムリソースを解放します
fclose(fp);
return(0);
}
テキストファイルfile.txtがあると仮定します。その内容は次のとおりです:
这里是 example.com
上記のプログラムをコンパイルして実行すると、出力結果は次のようになります:
这里是 example.com
プログラムはファイルの全内容を正しく出力し、余分な文字化け文字はありません。
よくある误区:なぜ feof() をループ条件に置いてはいけないのか
多くの初心者は次のような書き方をします。より簡潔に見えますが、実は誤りです:
誤った例
int main ()
{
FILE *fp;
int c;
fp = fopen("file.txt","r");
if(fp == NULL)
{
perror("ファイルを開くときにエラーが発生しました");
return(-1);
}
// 誤り:feof() をループの入口条件として使用している
while( !feof(fp) )
{
c = fgetc(fp);
printf("%c", c);
}
fclose(fp);
return(0);
}
この誤った書き方の出力結果は次のようになります:
这里是 example.com
末尾に余分な文字化け文字が1つあります。なぜこうなるのか?以下でループの実行過程を1ラウンドずつ追跡します:
| ループのラウンド数 | ループ入口の feof() | fgetc() が読み取ったもの | printf() が出力したもの | 説明 |
|---|---|---|---|---|
| 第1ラウンド | 0 を返す(まだ読んでいない) | 'これ' | この | 正常 |
| 第2ラウンド | 0 を返す(まだ末尾に達していない) | '里' | 里 | 正常 |
| ... | ... | ... | ... | 途中の文字は正常に出力される |
| 最後のラウンド | 0 を返す(まだ末尾に達していない) | 'm' | m | 最後の正常な文字 |
| 余分なラウンド | 0 を返す(注意!この時点では 'm' を読み終えたばかりで、feof はまだ末尾に達したことを知らない) | EOF(-1) | (文字化け) | bug!1回多く読み取ってしまった |
問題の根源は次のとおりです:ちょうど 'm'(ファイルの最後の文字)を読み終えたとき、feof() は依然として 0 を返します。
もう一度「読み取ろうと試みて」、読み取る内容がもうないことを発見して初めて、内部マークを「終了」にめくります。
したがって while(!feof(fp)) という書き方はループを1ラウンド多く実行させます。余分なラウンドでは、fgetc() がEOF(-1)を返し、printf が -1 を文字として出力しようとするため、文字化けが発生します。
一言で覚える:feof() は「事後の報告」であり、「事前の警告」ではありません。それが答えるのは「前回読んだときに末尾にぶつかったかどうか」であり、「次に読んだときに末尾にぶつかるかどうか」ではありません。したがって、feof() をループの入口条件として決して使用しないでください。
正しいパターンは前述の例のような書き方です——先に読み取り、それから判断し、安全を確認した後に読み取った内容を使用します。
いつ feof() を使うのか
feof() は主に以下の2つのシナリオで使用されます:
| シナリオ | 用法 | 例 |
|---|---|---|
| ループでのファイル読み取り | 先に読んでから判断する(推奨) | while(1) { c = fgetc(fp); if(feof(fp)) break; ... } |
| 読み取りエラーの調査 | 「正常終了」と「読み取りエラー」を区別する | if(feof(fp)) { /* 正常に読み終わり */ } else if(ferror(fp)) { /* 読み取りエラー */ } |
2つ目のシナリオでは、fgetc() が EOF を返す理由は2つあります:本当に読み終わった(feof() で判断)、または読み取り処理中にエラーが発生した(ferror() で判断)。両者は区別して処理する必要があります。
その他の拡張
C 標準ライブラリ - <stdio.h>