programing

왜 "while(!feof(file)"은 항상 잘못된 것일까요?

cafebook 2023. 4. 13. 21:10
반응형

왜 "while(!feof(file)"은 항상 잘못된 것일까요?

를 사용하는 데 어떤 문제가 있습니까?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 시스템에 대해 물어볼 수 있습니다.하지만 그 대답은 일반적으로 실행이 불가능하기 때문에 의미가 없다.컨테이너에 「네」라고 하는 경우는, 읽기를 시도할 때까지 데이터가 없는 경우가 있습니다.마찬가지로, "아니오"라고 대답한 경우, 읽기를 시도할 때쯤이면 데이터가 도착했을 수 있습니다.결론은 가능한 답변에 대해 의미 있는 행동을 할 수 없기 때문에 "데이터가 있다"와 같은 속성은 존재하지 않는다는 것이다(버퍼 입력이 있으면 상황은 약간 좋아지고, 어떤 종류의 보증을 구성하는 "네, 데이터가 있다"를 얻을 수 있다).하지만 당신은 여전히 그 반대 사건을 처리할 수 있을 겁니다.그리고 출력의 경우, 그 디스크나 네트워크 버퍼가 꽉 찼는지 알 수 없는 상황이 되는 것은 확실합니다.

따라서 I/O 시스템에 I/O 작업을 수행할 수 있는지 여부를 묻는 것은 불가능하며 사실 불합리합니다.(동시 컨테이너와 마찬가지로) 조작을 시도하여 성공 여부를 확인하는 방법밖에 없습니다.환경과 상호작용하는 그 순간에는 상호작용이 실제로 가능했는지 여부를 알 수 있으며, 그 시점에서 상호작용을 수행할 것을 약속해야 합니다.(이것은 「동기화 포인트」입니다).

EOF

이제 EOF에 도달합니다.EOF는 I/O 작업을 시도했을 때 받는 응답입니다.즉, 무언가를 읽거나 쓰려고 했지만, 그 때 데이터를 읽거나 쓸 수 없었고, 대신 입력 또는 출력의 끝이 발생했음을 의미합니다.이는 C 표준 라이브러리, C++ iostream 또는 기타 라이브러리를 불문하고 기본적으로 모든 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읽어낸 요소의 수(제로에 불과할 수 있습니다).

  • Cstdio,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()discloss.discloss.conf.

  • 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에 도달했을 경우.

실제 단어 "EOF"는 거의 철자를 쓰지 않습니다.통상, 에러 상태는, 보다 곧바로 검출할 수 있는 다른 방법으로 검출됩니다(예: 원하는 만큼의 I/O를 실행할 수 없는 경우).모든 예에서 EOF 상태가 발생했음을 명시적으로 알려줄 수 있는 API 기능이 있지만, 이는 사실 매우 유용한 정보는 아닙니다.그것은 우리가 종종 신경 쓰는 것보다 훨씬 더 세부적인 것이다.중요한 것은 I/O의 성공 여부이며 실패 경위보다 성공 여부입니다.

  • 실제로 EOF 상태를 쿼리하는 마지막 예를 다음에 나타냅니다.문자열이 있고 문자열이 공백 이외의 추가 비트를 포함하지 않고 전체 정수를 나타내는지 테스트한다고 가정합니다.C++ iostream을 사용하면 다음과 같이 됩니다.

      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
      }
    

여기서는 두 가지 결과를 사용합니다. 번째는 the the the입니다.iss이 ""로 되어 있는지 value에 또 연산을 하게 , 바로 I/O/연산을 하다.iss.get()포맷된 추출에 의해 문자열 전체가 이미 소비된 경우 EOF로 실패할 것으로 예상됩니다.

에서는 C와 것을 수 .strto*l는 엔드 포인터가 입력 문자열의 끝에 도달했는지 확인하는 것으로 기능합니다.

이 오류는 (읽기 오류가 없는 경우) 작성자가 예상한 것보다 한 번 더 루프에 들어가기 때문에 올바르지 않습니다.읽기 에러가 발생했을 경우, 루프는 종료하지 않습니다.

다음 코드를 고려합니다.

/* 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;
}

이 프로그램은 입력 스트림에 있는 문자 수보다 한 글자 많은 문자를 지속적으로 인쇄합니다(읽기 오류가 없는 경우).입력 스트림이 비어 있는 경우를 생각해 보겠습니다.

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

「」는,feof()거짓됩니다.fgetc() 반환)EOF카운트가 증가합니다. ★★★★★★★★★★★★★★★.feof()사실

을 사용법 feof()스트림의 읽기가 파일 끝에 도달할 까지 true를 반환하지 않습니다.의 목적feof()는, 의 판독치가.★★★의 feof()는 이전 읽기 함수의 상태를 판별하고 오류 조건과 데이터 스트림의 끝을 구별하는 것입니다. iffread()하고 0을 사용해야 .사용자는 다음 명령을 사용해야 합니다.feof/ferror오류가 발생했는지 또는 모든 데이터가 소비되었는지 여부를 판단합니다.로 「」의 경우fgetcEOFfeof()fread가 0을 반환한 후에만 유효합니다.fgetcEOFfeof() 010을 합니다.

)을.fread() , 「」, 「」fscanf() , 「」, 「」fgetc()를 호출하기 에 )을 클릭합니다.feof().

게다가 읽기 에러가 발생하는 경우도 있습니다. 경우, 「 」fgetc()EOF,feof()거짓, " "는while(!feof(p))경우, 루프 「」, 「」의 가 있습니다.ferror() 「」, 「」, 「」, 「」를 참조해 주세요.while(!feof(p) && !ferror(p))또는 무효 데이터가 처리될 때 모든 종류의 가비지를 뿜어내는 무한 루프 가능성이 매우 높습니다.

하자면, '아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아while(!feof(f))(읽기 오류에 대한 무한 루프를 피하기 위해 중단이 있는 루프 내부에 또 다른 체크가 있어야 하지만) 거의 항상 잘못된 경우입니다.그리고 설령 그것이 옳을 만한 사건이 발생하더라도, 그것은 너무 관용적으로 잘못되었기 때문에 코드를 쓰는 올바른 방법이 아닐 것이다.이 코드를 보는 사람은 즉시 주저하며 "그건 버그입니다."라고 말해야 합니다.그리고 작성자를 때립니다(작성자가 당신의 상사가 아닌 경우).

아, 、 상상 、 상린야야야 。we file "while do not past end of file"을 합니다.while (!feof(f))그러나 이것은 일반적인 루프 상태가 아닙니다.더 읽을 수 있어요. while (!feof(f))틀린 게 아니라 그냥 잘못 쓴 거예요

feof()파일 끝을 지나 읽으려고 시도했는지 여부를 나타냅니다.이는 예측 효과가 거의 없음을 의미합니다.참일 경우 다음 입력 조작이 실패할 것이라고 확신하지만(이전 입력 조작이 실패했다고 확신할 수 없지만), 거짓일 경우 다음 입력 조작이 성공할지 확신할 수 없습니다.또한 입력 조작은 파일 종료 이외의 이유로 실패할 수 있습니다(모든 종류의 입력에 대해 포맷된 입력에 대한 포맷 오류, 순수 IO 오류(디스크 장애, 네트워크 타임아웃)). 생략이 에서 처리 전 않은 을 미칠 수 있습니다.는 공간을 건너뛸 필요가 있는 경우 복잡해질 수 있으며 인터랙티브디바이스에 바람직하지 않은 영향을 미칠 수 있음을 나타냅니다.경우에 따라서는 이전 행의 처리를 시작하기 전에 다음 행의 입력을 강제하는 경우가 있습니다).장애를 처리할 수 있어야 합니다.

따라서 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) */
}

feof()하다제 아주 겸손한 의견으로는FILE의 파일 는 「파일 종료」로 가 있습니다true파일 、 달달도도도도 경경경경 。대신 각 읽기 작업 후 파일 끝에 도달했는지 수동으로 확인해야 합니다. 이런 때, 이 을 읽을 때 할 수 있다, 수 있다, 수 있다, 수 있다, 이런 식으로요.fgetc():

#include <stdio.h>

int main(int argc, char *argv[])
{
  FILE *in = fopen("testfile.txt", "r");

  while(1) {
    char c = fgetc(in);
    if (feof(in)) break;
    printf("%c", c);
  }

  fclose(in);
  return 0;
}

대신 다음과 같은 것이 작동했으면 합니다.

#include <stdio.h>

int main(int argc, char *argv[])
{
  FILE *in = fopen("testfile.txt", "r");

  while(!feof(in)) {
    printf("%c", fgetc(in));
  }

  fclose(in);
  return 0;
}

언급URL : https://stackoverflow.com/questions/5431941/why-is-while-feoffile-always-wrong

반응형