요즘 많은 게시물에서 이와 같은 파일을 읽으려는 사람들을 보았습니다.
#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;
}
이 루프에 어떤 문제가 있습니까?
답변
추상적이고 높은 수준의 관점을 제공하고 싶습니다.
동시성과 동시성
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 < bufsize) { 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 ++, iostream 형식의 추출 :
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 ++ 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스트림 객체 자체이며 형식화 된 추출이value성공 했는지 확인합니다 . 그러나 공백을 사용한 후에도 다른 I / O / 연산을 수행iss.get()하고 EOF로 실패 할 것으로 예상됩니다. 이는 전체 문자열이 이미 형식화 된 추출에 의해 소비 된 경우입니다.C 표준 라이브러리
strto*l에서는 끝 포인터가 입력 문자열의 끝에 도달했는지 확인 하여 함수 와 비슷한 것을 얻을 수 있습니다 .
대답
while(!feof)관련이없는 것을 테스트하고 알아야 할 것을 테스트하지 않기 때문에 잘못되었습니다. 결과적으로 실제로 이런 일이 발생하지 않았을 때 성공적으로 읽은 데이터에 액세스한다고 가정하는 코드를 잘못 실행하고 있습니다.
답변
읽기 오류가 없으면 작성자가 예상 한 것보다 한 번 더 루프에 들어가기 때문에 잘못되었습니다. 읽기 오류가 있으면 루프가 종료되지 않습니다.
다음 코드를 고려하십시오.
/* 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)하며 카운트가 증가합니다. 그런 다음 feof()호출되어 true를 반환하여 루프가 중단됩니다.
이것은 모든 경우에 발생합니다. feof()때까지 true를 반환하지 않습니다 후 스트림의 읽기는 파일의 끝을 발견. 목적은 feof()다음 읽기가 파일의 끝에 도달하는지 확인하지 않는 것입니다. feof()읽기 오류와 파일 끝에 도달 한 것을 구별 하는 것이 목적입니다 . 경우 fread()0을 반환, 당신은 사용해야합니다 feof/ ferror오류가 발생했습니다 또는 모든 데이터가 소모 된 경우 여부를 결정합니다. 마찬가지로 if를 fgetc반환합니다 EOF. fread가 0 을 반환 하거나 반환 한 후에feof() 만 유용합니다 . 그 전에 항상 0을 반환합니다.fgetcEOFfeof()
호출하기 전에 항상 읽기의 반환 값 ( fread(), 또는 fscanf(), 또는 fgetc())을 확인해야 feof()합니다.
더 나쁜 것은 읽기 오류가 발생하는 경우를 고려하십시오. 이 경우, fgetc()반환 EOF, feof()false를 반환하고, 루프는 종료하지 않습니다. while(!feof(p))사용되는 모든 경우 에 대해 적어도 루프 내부에 대한 점검이 ferror()있거나 최소한 while 조건이 대체되어야 while(!feof(p) && !ferror(p))하거나 무한 루프의 실제 가능성이있을 수 있습니다. 유효하지 않은 데이터가 처리되고 있습니다.
따라서 요약하자면, ” while(!feof(f))“라고 쓰는 것이 의미 론적으로 올바른 상황은 없다고 확신 할 수는 없지만 ( 읽기 오류에서 무한 루프를 피하기 위해 중단으로 루프 내부에 다른 검사 가 있어야 하지만) ), 거의 항상 틀린 경우입니다. 그리고 올바른 경우가 발생하더라도 관용적으로 잘못되어 코드를 작성하는 올바른 방법이 아닐 수 있습니다. 이 코드를 보는 사람은 즉시 주저하고 “버그입니다”라고 말합니다. 저자가 당신의 상사 인 경우를 제외하고 저자를 때릴 수 있습니다.
답변
아니요 항상 틀린 것은 아닙니다. 루프 조건이 “파일의 과거 끝을 읽으려고 시도하지 않은 동안”인 경우을 사용 while (!feof(f))합니다. 그러나 이것은 일반적인 루프 조건이 아닙니다. 일반적으로 다른 것을 테스트하려고합니다 (예 : “더 읽을 수 있습니까”). while (!feof(f))잘못이 아니고 잘못 사용되었습니다 .
답변
feof()파일 끝을 지나서 읽으려고했는지 여부를 나타냅니다. 즉, 예측 효과가 거의 없음을 의미합니다. 사실 경우 다음 입력 작업이 실패 할 것이라고 확신하지만 (이전 작업이 BTW에 실패했는지 확실하지 않은 경우) 거짓 인 경우 다음 입력이 확실하지 않습니다 작업이 성공합니다. 또한 파일 끝 이외의 다른 이유로 입력 작업이 실패 할 수 있습니다 (형식화 된 입력의 경우 형식 오류, 순수한 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) */
}
답변
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;
}
답변
