1. ホーム
  2. c

[解決済み] while ( !feof (file) ) 」は、なぜいつも間違っているのですか?

2022-03-15 04:11:55

質問

を使用すると、何が問題なのでしょうか? 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;
}

このループのどこが悪いのでしょうか?

解決方法は?

TL;DR

while(!feof) は、無関係なものをテストし、知る必要のあるものをテストしていないため、間違っています。その結果、読み込みに成功したデータにアクセスしていると思い込んでいるコードを誤って実行することになります。

抽象的でハイレベルな視点を提供したいと思います。ということに興味がある方は読み進めてください。 while(!feof) を実際に行っています。

並行性と同時性

I/O操作は環境と相互作用します。環境はあなたのプログラムの一部でもなければ、あなたのコントロール下でもありません。環境は本当にあなたのプログラムと "同時に"存在するのです。すべての並行処理がそうであるように、「現在の状態」についての質問は意味をなさない。同時発生するイベントには「同時性」という概念はありません。状態の多くの特性は、単に 存在する を同時進行させる。

もう少し正確に説明します。例えば、「もっとデータがあるのか」と尋ねるとします。これを同時実行コンテナやI/Oシステムに尋ねることができます。しかし、その答えは一般的にアクション不可能であり、したがって意味がありません。コンテナが「はい」と答えたらどうなるでしょうか。同様に、もし答えが「いいえ」であったとしても、あなたが読もうとしたときにはデータが届いているかもしれません。結論は、単純に のような性質はありません。なぜなら、どのような答えが返ってきても、それに対して意味のある行動をとることができないからです。(バッファリングされた入力では、ある種の保証を構成する "yes, I have data" が得られる可能性があり、状況は少し良くなりますが、それでも逆のケースに対処できるようにしなければならないでしょう)。出力に関しては、私が説明したように、状況は確かに悪いです。)

ということで、不可能であり、実際には不可能であると結論づけました。 合理的 という問いかけをI/Oシステムに対して行うことです。 になります。 を実行することができます。これと対話できる唯一の方法は(同時実行コンテナと同じように)、次のとおりです。 試みる を実行し、成功したか失敗したかを確認します。環境と対話するその瞬間に、その対話が実際に可能であったかどうかを知ることができ、その時点で対話の実行をコミットしなければならない。(そしてその時点で、あなたはそのインタラクションを実行することを約束しなければなりません(これは、言うなれば「同期ポイント」です)。

EOF

さて、次はEOFです。EOFは 応答 から得られる 試される I/O操作。これは、何かを読み書きしようとしたが、その際にデータの読み書きに失敗し、代わりに入力または出力の終了に遭遇したことを意味します。これは、Cの標準ライブラリ、C++のiostreams、その他のライブラリなど、基本的にすべてのI/O APIに当てはまります。I/O操作が成功しさえすれば、単に 知ることができない さらに将来の操作が成功するかどうか。あなたは しなければならない は、必ず最初に操作を試し、成功か失敗かを応答する。

使用例

それぞれの例で、以下の点に注意してください。 まず I/O操作を試行し その後 その結果が有効であれば、それを消費する。さらに、我々は 常に はI/O操作の結果を使わなければならないが、その結果はそれぞれの例で異なる形や形態をとる。

  • C stdioで、ファイルから読み込む。

      for (;;) {
          size_t n = fread(buf, 1, bufsize, infile);
          consume(buf, n);
          if (n == 0) { break; }
      }
    
    

    私たちが使わなければならない結果は n は、読み込まれた要素の数(0になることもある)です。

  • C stdioです。 scanf :

      for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
          consume(a, b, c);
      }
    
    

    の戻り値を使用する必要があります。 scanf は、変換された要素の数です。

  • C++、iostreams フォーマットの抽出。

      for (int n; std::cin >> n; ) {
          consume(n);
      }
    
    

    私たちが使わなければならない結果は std::cin これはブーリアンコンテキストで評価され、ストリームがまだ good() の状態になります。

  • C++、iostreams getline。

      for (std::string line; std::getline(std::cin, line); ) {
          consume(line);
      }
    
    

    私たちが使用しなければならない結果は、再び std::cin 先ほどと同じように

  • POSIXです。 write(2) でバッファをフラッシュします。

      char const * p = buf;
      ssize_t n = bufsize;
      for (ssize_t k = bufsize; (k = write(fd, p, n)) > 0; p += k, n -= k) {}
      if (n != 0) { /* error, failed to write complete buffer */ }
    
    

    ここで使用する結果は k は、書き込まれたバイト数です。ここで重要なのは、何バイト書き込まれたかを知ることができるのは 書き込み操作を行った。

  • POSIX getline()

      char *buffer = NULL;
      size_t bufsiz = 0;
      ssize_t nbytes;
      while ((nbytes = getline(&buffer, &bufsiz, fp)) != -1)
      {
          /* Use nbytes of data in buffer */
      }
      free(buffer);
    
    

    私たちが使わなければならない結果は nbytes は、改行(ファイルが改行で終わっていない場合はEOF)までのバイト数です。

    この関数は、明示的に -1 (エラーが発生したときやEOFに到達したときは、(EOFではなく!)。

EOF"という単語を実際に綴ることはほとんどないことにお気づきでしょう。通常、私たちはもっとすぐに興味を引かれるような他の方法でエラー状態を検出します(例えば、望んでいたほど多くのI/Oを実行できなかった場合など)。どの例でも、EOF状態に遭遇したことを明示的に伝えることができるAPI機能がありますが、これは実際にはあまり有用な情報ではありません。私たちがしばしば気にするよりも、ずっと細かいことなのだ。重要なのは、I/Oが成功したかどうかであり、どのように失敗したかよりも重要なのです。

  • 最後に、実際にEOFの状態を問い合わせる例を挙げます。文字列があり、それが空白以外の余分なビットがない、完全な整数を表していることをテストしたいとします。C++のiostreamsを使うと、このようになります。

      std::string input = "   123   ";   // example
    
      std::istringstream iss(input);
      int value;
      if (iss >> value >> std::ws && iss.get() == EOF) {
          consume(value);
      } else {
          // error, "input" is not parsable as an integer
      }
    
    

ここでは2つの結果を使用します。1つ目は iss にフォーマットされた抽出が、ストリーム・オブジェクトそのものであることを確認するためです。 value が成功しました。しかし、その後、空白も消費した後、別のI/O/操作を実行します。 iss.get() これは、文字列全体がすでにフォーマットされた抽出によって消費されている場合に起こります。

C の標準ライブラリでは、同様のことを strto*l 関数は、エンドポインタが入力文字列の末尾に到達したことを確認することによって、入力文字列の末尾に到達したことを確認します。