642

feof()読み取りループを制御するために使用することの何が問題になっていますか? 例えば:

#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char **argv)
{
    char *path = "stdin";
    FILE *fp = argc > 1 ? fopen(path=argv[1], "r") : stdin;

    if( fp == NULL ){
        perror(path);
        return EXIT_FAILURE;
    }

    while( !feof(fp) ){  /* THIS IS WRONG */
        /* Read and process data from file… */
    }
    if( fclose(fp) != 0 ){
        perror(path);
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

このループの何が問題になっていますか?

4

5 に答える 5

268

(読み取りエラーがなければ) 作成者が予想するよりも 1 回ループに入るからです。読み取りエラーが発生した場合、ループは終了しません。

次のコードを検討してください。

/* WARNING: demonstration of bad coding technique!! */

#include <stdio.h>
#include <stdlib.h>

FILE *Fopen(const char *path, const char *mode);

int main(int argc, char **argv)
{
    FILE *in;
    unsigned count;

    in = argc > 1 ? Fopen(argv[1], "r") : stdin;
    count = 0;

    /* WARNING: this is a bug */
    while( !feof(in) ) {  /* This is WRONG! */
        fgetc(in);
        count++;
    }
    printf("Number of characters read: %u\n", count);
    return EXIT_SUCCESS;
}

FILE * Fopen(const char *path, const char *mode)
{
    FILE *f = fopen(path, mode);
    if( f == NULL ) {
        perror(path);
        exit(EXIT_FAILURE);
    }
    return f;
}

このプログラムは、入力ストリーム内の文字数よりも 1 大きい値を一貫して出力します (読み取りエラーがないことを前提としています)。入力ストリームが空の場合を考えてみましょう:

$ ./a.out < /dev/null
Number of characters read: 1

この場合、feof()はデータが読み取られる前に呼び出されるため、false を返します。ループに入り、fgetc()が呼び出され (そして が返されEOF)、 count がインクリメントされます。その後、feof()が呼び出されて true を返し、ループが中止されます。

これは、そのようなすべての場合に発生します。 ストリームの読み取りがファイルの終わりに到達するまでfeof()true を返しません。の目的は、次の読み取りがファイルの終わりに到達するかどうかを確認することではありません。の目的は、以前の読み取り関数のステータスを判断し、エラー状態とデータ ストリームの終了を区別することです。0 を返す場合は、 /を使用して、エラーが発生したか、またはすべてのデータが消費されたかを判断する必要があります。同様に if を返します。 fread が 0 を返した後、または fread が を返した後にのみ有用です。それが起こる前は、常に 0 を返します。feof()feof()fread()feofferrorfgetcEOFfeof()fgetcEOFfeof()

を呼び出す前に、読み取り ( 、または 、または のいずれか)のfread()戻り値を常にチェックする必要があります。fscanf()fgetc()feof()

さらに悪いことに、読み取りエラーが発生した場合を考えてみましょう。その場合、 をfgetc()返しEOFfeof()false を返し、ループは決して終了しません。が使用されるすべての場合においてwhile(!feof(p))、少なくとも for のループ内でチェックが必要ですferror()。または、少なくとも while 条件を に置き換える必要があります。そうしwhile(!feof(p) && !ferror(p))ないと、無限ループの非常に現実的な可能性があり、おそらくあらゆる種類のゴミを次のように吐き出します。無効なデータが処理されています。

したがって、要約すると、" " と書くことが意味的に正しいかもしれないという状況は絶対にないと断言することはできませんwhile(!feof(f))(ただし、読み取りエラーで無限ループを回避するために、ループ内で別のチェックをブレーク付きで行う必要があります)。 )、ほぼ確実に間違っている場合です。そして、それが正しいケースが発生したとしても、それは慣用的に間違っているため、コードを書く正しい方法ではありません。そのコードを見た人は、すぐに躊躇して「これはバグだ」と言うべきです。そして、作者を平手打ちする可能性があります (ただし、作者があなたの上司である場合を除きます。その場合、裁量が推奨されます)。

于 2011-03-25T12:39:08.600 に答える
73

いいえ、必ずしも間違っているわけではありません。ループ条件が「ファイルの終わりを過ぎて読み取ろうとしていない間」の場合は、を使用しますwhile (!feof(f))。ただし、これは一般的なループ条件ではありません。通常は、他の何か (「もっと読むことができますか」など) をテストする必要があります。使い方がwhile (!feof(f))間違っているだけです。

于 2011-03-25T11:49:12.527 に答える
44

feof()ファイルの終わりを超えて読み込もうとしたかどうかを示します。つまり、予測効果はほとんどありません。trueの場合、次の入力操作が失敗することは確かですが(前の操作が失敗したかどうかはわかりません)、falseの場合、次の入力は確実ではありません。操作は成功します。さらに、入力操作は、ファイルの終わり以外の理由(フォーマットされた入力のフォーマットエラー、すべての入力の種類の純粋なIO障害(ディスク障害、ネットワークタイムアウト))で失敗する可能性があるため、ファイルの終わり(および予測可能なAda oneを実装しようとした人は、スペースをスキップする必要がある場合は複雑になる可能性があり、インタラクティブデバイスに望ましくない影響を与える可能性があることを教えてくれます-時には次の入力を強制します前の行の処理を開始する前の行)、

したがって、Cの正しいイディオムは、IO操作の成功をループ条件としてループし、失敗の原因をテストすることです。例えば:

while (fgets(line, sizeof(line), file)) {
    /* note that fgets don't strip the terminating \n, checking its
       presence allow to handle lines longer that sizeof(line), not showed here */
    ...
}
if (ferror(file)) {
   /* IO failure */
} else if (feof(file)) {
   /* format error (not possible with fgets, but would be with fscanf) or end of file */
} else {
   /* format error (not possible with fgets, but would be with fscanf) */
}
于 2012-02-10T10:22:04.530 に答える