programing

왜 "while (!fe (file) )"가 항상 틀리는가?

prostudy 2022. 5. 18. 21:57
반응형

왜 "while (!fe (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 시스템에 대해 물어볼 수 있다.그러나 그 대답은 일반적으로 실행이 불가능하며, 따라서 무의미하다.따라서 용기가 "예"라고 말하면 – 여러분이 읽으려고 할 때쯤에는 더 이상 데이터가 없을 수도 있다.마찬가지로, 만약 당신이 "아니오"라고 대답한다면, 당신이 읽으려고 할 때쯤에는 데이터가 도착했을지도 모른다.결론은 "나는 데이터가 있다"와 같은 속성은 없다는 이다. 왜냐하면 당신은 가능한 어떤 대답에 대해 의미 있게 행동할 수 없기 때문이다. (상황은 버퍼링된 입력으로 조금 더 나아지는데, 여기서 당신은 어떤 종류의 보증을 구성하는 "예, 나는 데이터가 있다"를 얻을 수 있지만, 당신은 여전히 반대되는 것을 다룰 수 있어야 한다.te case그리고 출력을 사용하면 상황은 확실히 내가 설명한 것만큼 나쁘다. 디스크나 네트워크 버퍼가 꽉 찼는지 당신은 절대 모른다.)

따라서 우리는 I/O 시스템에 I/O 작업을 수행할 수 있는지 여부를 묻는 것은 불가능하며, 사실상 불합리하다고 결론짓는다.(동시컨테이너와 마찬가지로) 그것과 상호작용을 할 수 있는 유일한 방법은 작업을 시도해서 그것이 성공했는지 실패했는지 확인하는 것이다.환경과 상호작용하는 그 순간, 그리고 그때에야 비로소 상호작용이 실제로 가능한지 알 수 있으며, 그 시점에서 상호작용을 수행하기로 약속해야 한다.(이것은 "동기화 지점"입니다, 만약 그렇다면)

EOF

이제 EOF로 가자EOF는 I/O를 시도한 작업에서 얻을 수 있는 응답이다.무엇을 읽거나 쓰려고 하다가 어떤 데이터도 읽거나 쓰지 못하고 대신 입력이나 출력의 끝이 마주쳤다는 뜻이다.이는 C 표준 라이브러리, C++ iostreams 또는 기타 라이브러리가 되든 기본적으로 모든 I/O API에 적용된다.I/O 작전이 성공하는 한 앞으로의 작전이 성공할지 알 수 없다.당신은 항상 먼저 수술을 시도하고 나서 성패에 대응해야 한다.

각 예제에서 먼저 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++, iostream getline:

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

    우리가 반드시 사용해야 하는 결과는 또 있다.std::cin아까처럼.

  • 포식스,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, 쓴 바이트 수입니다.여기서 요점은 쓰기 작업 에 쓰여진 바이트의 수만을 알 수 있다는 것이다.

  • 포식스

      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++ 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
      }
    

여기서 두 가지 결과를 사용한다.는 이다.iss, , Step 객체 자 자 자 자 자 자 인 인 인 인 인 인인.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()호출되고 true로 반환되어 루프가 중단된다.

이 모든 경우에 이런 일이 일어난다.feof()스트림에서 읽은 파일이 파일 끝에 도달할 때까지 참으로 반환되지 않음.의 목적feof()다음 읽기가 파일 끝에 도달하는지 확인하는 것이 아니다.의 취지는 의 .feof()이전 읽기 기능의 상태를 결정하고 오류 조건과 데이터 스트림의 끝을 구별하는 것이다.만약fread()리턴스 0, 사용해야 함feof/ferror오류가 발생했는지 또는 모든 데이터가 사용되었는지 여부를 결정한다.유사하게 다음과 같은 경우fgetc돌아온다EOF.feof()fread가 0을 반환한 에만 유용하다.fgetc돌아왔다EOF그런 전에feof()항상 0을 반환할 것이다.

판독치의 반환 값(둘 중 하나)을 항상 확인해야 한다.fread(), 또는 .fscanf(), 또는 .fgetc()전화하기 전에 )feof().

더 심각한 것은 읽기 오류가 발생한 경우를 고려해보자.그 경우에는fgetc()돌아온다EOFfeof()거짓으로 돌아왔고, 루프는 절대 끝나지 않아모든 경우에서 다음과 같은 경우while(!feof(p))사용되며, 적어도 루프 내부에 다음 작업을 위한 점검이 있어야 한다.ferror(), 또는 적어도 그 동안 상태는 다음으로 대체되어야 한다.while(!feof(p) && !ferror(p))아니면 무한 루프(infinite loop)의 가능성이 매우 높으며, 아마도 모든 종류의 쓰레기를 유효하지 않은 데이터가 처리되고 있을 것이다.

그래서 요약하자면, 비록 내가 "라고 쓰는 것이 의미론적으로 옳을 수 있는 상황은 결코 존재하지 않는다고 확실하게 말할 수는 없지만.while(!feof(f))(읽기 오류에 대한 무한 루프를 피하기 위해 끊어진 루프 내부에 또 다른 점검이 있어야 하지만) 거의 틀림없이 항상 틀리는 경우다.그리고 설사 그것이 옳을 만한 곳에서 사건이 발생했다고 하더라도, 그것은 너무나 관용적으로 잘못되어 코드를 쓰는 올바른 방법이 아닐 것이다.그 코드를 본 사람은 즉시 주저하면서 "거시기, 벌레다"라고 말해야 한다.그리고 저자를 때릴 수도 있다(저자가 당신의 상사가 아니라면, 재량권이 권고된다).

feof()파일 끝을 지나 읽으려고 했는지 여부를 표시한다.즉, 예측 효과가 거의 없다는 것을 의미하는데, 만약 그것이 사실이라면 다음 입력 연산이 실패할 것이라고 확신하지만(이전의 입력 연산이 BTW 실패라고 확신할 수 없다), 그것이 거짓이라면 다음 입력 연산이 성공할 것이라고 확신할 수 없다.또한, 파일 끝(모든 입력 종류에 대한 포맷 오류, 순수 입출력 오류, 디스크 오류, 네트워크 시간 초과) 이외의 이유로 입력 작업이 실패할 수 있으므로 파일 끝에 대해 예측할 수 있더라도(그리고 예측 가능한 Ada 1을 구현하려고 시도한 모든 사용자는 y가 복잡할 수 있다고 말할 것이다.공간을 건너뛰어야 하며, 대화형 장치에 바람직하지 않은 영향을 미치며, 때로는 이전 행의 처리를 시작하기 전에 다음 행의 입력을 강제한다.) 실패를 처리할 수 있어야 한다.

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

아니 항상 틀린 것은 아니다.루프 조건이 "파일이 지난 후 읽으려고 시도하지 않은 동안"인 경우while (!feof(f))그러나 이것은 일반적인 루프 조건이 아니다. 일반적으로 다른 것에 대해 테스트하기를 원한다(예: "더 읽어도 돼").while (!feof(f))틀린 게 아니라 잘못 사용된 것뿐이에요

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-feof-file-always-wrong

반응형