루프 조건 (즉,`(while (! stream.eof ())`) 내부의 iostream :: eof가 왜 잘못된 것으로 간주됩니까?


595

루프 응답에서 사용 하는 것이 "거의 확실하다"는 답변 의 주석을 찾았습니다 iostream::eof. 나는 일반적으로 while(cin>>n)EOF를 암시 적으로 확인하는 것 같은 것을 사용합니다 .

eof를 명시 적으로 while (!cin.eof())잘못 검사하는 이유는 무엇 입니까?

scanf("...",...)!=EOFC 에서 사용하는 것과 어떻게 다른 가요?


21
scanf(...) != EOFscanf구문 분석 및 할당 된 필드 수를 반환 하므로 C에서도 작동하지 않습니다 . 정확한 조건은 scanf(...) < n어디 n형식 문자열 필드의 수입니다.
벤 Voigt

5
@Ben Voigt는 EOF에 도달 한 경우 음수 (일반적으로 EOF가 정의 됨)를 반환합니다.
Sebastian

19
@SebastianGodelet : 실제로, EOF첫 번째 필드 변환 전에 파일 끝이 발견되면 (성공 여부) 리턴 합니다. 필드 사이에 파일 끝에 도달하면 성공적으로 변환되어 저장된 필드 수를 리턴합니다. 어느 것이 EOF잘못 비교 하는가.
벤 Voigt

1
@SebastianGodelet : 아뇨. 그는 "루프를 지나쳐서 부적절한 입력과 올바른 입력을 구별 할 수있는 쉬운 방법이 없다"고 잘못 지적했다. 실제로 .eof()루프 종료 후 확인하는 것만 큼 쉽습니다 .
벤 Voigt

2
@Ben 예,이 경우 (간단한 정수 읽기). 그러나 while(fail)실제 장애와 eof 로 루프가 종료 되는 시나리오를 쉽게 만들 수 있습니다 . 반복 당 3 개의 정수가 필요한지 (예를 들어 xyz 포인트 또는 무언가를 읽는 중), 스트림에 2 개의 정수 만 있는지 생각해보십시오.
교활한

답변:


544

스트림의 끝을 읽은 후에iostream::eof 만 반환 되기 때문 입니다 . 다음 읽기가 스트림의 끝임을 나타내지 는 않습니다 .true

이것을 고려하십시오 (그리고 다음 읽기가 스트림의 끝에 있다고 가정하십시오).

while(!inStream.eof()){
  int data;
  // yay, not end of stream yet, now read ...
  inStream >> data;
  // oh crap, now we read the end and *only* now the eof bit will be set (as well as the fail bit)
  // do stuff with (now uninitialized) data
}

이것에 대해

int data;
while(inStream >> data){
  // when we land here, we can be sure that the read was successful.
  // if it wasn't, the returned stream from operator>> would be converted to false
  // and the loop wouldn't even be entered
  // do stuff with correctly initialized data (hopefully)
}

두 번째 질문에서 :

if(scanf("...",...)!=EOF)

와 같다

if(!(inStream >> data).eof())

하지 와 같은

if(!inStream.eof())
    inFile >> data

12
언급 할 가치가 있다면 if (! (inStream >> data) .eof ())도 유용한 것을 수행하지 않는다는 것입니다. 오류 1 : 마지막 데이터 이후에 공백이 없으면 조건에 들어 가지 않습니다 (마지막 데이텀은 처리되지 않음). 오류 2 : EOF에 도달하지 않는 한 데이터 읽기에 실패한 경우에도 조건에 들어갑니다 (무한 루프, 동일한 이전 데이터를 반복해서 처리 함).
Tronic

4
나는이 대답이 약간 오도임을 지적 할 가치가 있다고 생각합니다. 추출 할 때 int의 나 std::string들 또는 유사한의 EOF 비트가 되어 당신이 끝나기 전에 한 권리를 추출 할 때 설정하고 추출 끝을 맞았습니다. 다시 읽을 필요는 없습니다. 파일을 읽을 때 설정되지 않는 이유 \n는 끝에 여분이 있기 때문 입니다. 나는 이것을 또 다른 대답으로 다루었 다 . 읽기 char는 한 번에 하나의 추출과 끝을 명중 계속하지 않기 때문에 s는 다른 문제입니다.
Joseph Mansfield

79
주요 문제는 우리가 EOF에 도달하지 않았다고해서 다음 읽기가 성공한다는 의미는 아닙니다 .
Joseph Mansfield

1
@sftrabbit : 모두 사실이지만 그다지 유용하지는 않습니다 ... 후행 '\ n'이 없더라도 다른 후행 공백이 파일 전체의 다른 공백과 일관되게 처리되도록하는 것이 합리적입니다 (즉, 건너 뜁니다). 또한 "이전에 추출 할 때"의 미묘한 결과 는 입력이 완전히 비어있을 때 s 또는 s while (!eof())에서 "작동" 하지 않기 때문에 후행 관리가 필요 하지 않다는 것조차 알 수 있습니다 . intstd::string\n
Tony Delroy

2
@TonyD 완전히 동의합니다. 내가 말하고 그 이유는 그들이이를 읽고 스트림이 포함되지 않은 경우는 비슷한 답변을 그렇게 생각합니다 때 대부분의 사람들이 생각하기 때문입니다 "Hello"(공백 또는 후행 \nA가와) std::string가에서 문자를 추출합니다 추출, Ho추출 정지하고, 그런 다음 EOF 비트를 설정 하지 마십시오 . 실제로, 추출을 중지 한 EOF이기 때문에 EOF 비트를 설정합니다. 사람들을 위해 그것을 정리하기를 바라고 있습니다.
Joseph Mansfield

103

결론 : 공백을 올바르게 처리하면 다음을 eof사용 하는 방법을 알 수 있습니다 ( fail()오류 검사 보다 더 안정적 임 ).

while( !(in>>std::ws).eof() ) {  
   int data;
   in >> data;
   if ( in.fail() ) /* handle with break or throw */; 
   // now use data
}    

( 답변을 강조해 줄 것을 제안 해준 Tony D에게 감사합니다. 이것이 더 강력한 이유에 대한 예는 아래의 주석을 참조하십시오. )


사용에 대한 주요 주장 eof()은 공백의 역할에 대한 중요한 미묘함이 누락 된 것 같습니다. 내 제안은 eof()명시 적으로 검사 하는 것이 " 항상 잘못 " 일뿐 만 아니라 이것과 비슷한 SO 스레드에서 가장 중요한 의견 인 것 같습니다. 그러나 공백을 올바르게 처리하면 더 깨끗하고 신뢰할 수 있습니다. 오류 처리이며 항상 올바른 솔루션입니다 (반드시 가장 열악한 것은 아니지만).

"적절한"종료 및 읽기 순서로 제안되는 내용을 요약하면 다음과 같습니다.

int data;
while(in >> data) {  /* ... */ }

// which is equivalent to 
while( !(in >> data).fail() )  {  /* ... */ }

eof 이후의 읽기 시도로 인한 실패는 종료 조건으로 간주됩니다. 이는 성공적인 스트림과 eof 이외의 이유로 실제로 실패한 스트림을 쉽게 구별 할 수있는 방법이 없음을 의미합니다. 다음 스트림을 취하십시오.

  • 1 2 3 4 5<eof>
  • 1 2 a 3 4 5<eof>
  • a<eof>

while(in>>data)세 입력 모두에 대해 세트 failbit로 종료됩니다 . 첫 번째와 세 번째 에도 설정됩니다. 따라서 루프를 지나면 적절한 입력 (1)과 부적절한 입력 (2 및 3)을 구별하기 위해 매우 추한 추가 논리가 필요합니다.eofbit

반면 다음을 수행하십시오.

while( !in.eof() ) 
{  
   int data;
   in >> data;
   if ( in.fail() ) /* handle with break or throw */; 
   // now use data
}    

여기에서 in.fail()읽을 것이있는 한 올바른 것이 맞는지 확인합니다. 목적은 단순한 while 루프 터미네이터가 아닙니다.

지금까지는 좋지만 스트림에 후행 공간이 있으면 어떻게 eof()되나요?

우리는 오류 처리를 포기할 필요가 없습니다. 그냥 공백을 먹으십시오.

while( !in.eof() ) 
{  
   int data;
   in >> data >> ws; // eat whitespace with std::ws
   if ( in.fail() ) /* handle with break or throw */; 
   // now use data
}

std::ws상기 설정 상태 공간 스트림 후행 잠재적 (0 개 이상) 스킵 eofbit하고, 하지를failbit . 따라서 in.fail()읽을 데이터가 하나 이상있는 한 예상대로 작동합니다. 모두 공백 스트림도 허용 가능한 경우 올바른 형식은 다음과 같습니다.

while( !(in>>ws).eof() ) 
{  
   int data;
   in >> data; 
   if ( in.fail() ) /* handle with break or throw */; 
   /* this will never fire if the eof is reached cleanly */
   // now use data
}

요약 : 올바르게 구성된 while(!eof)것은 가능하고 잘못되지 않았을뿐 아니라 데이터를 범위 내에서 현지화 할 수있게 해주 며 평소와 같이 비즈니스에서 오류 확인을 명확하게 분리 할 수 ​​있습니다. 말하자면 while(!fail), 더 일반적이고 간결한 관용구이며, 간단한 (읽기 유형별 단일 데이터) 시나리오에서 선호 될 수 있습니다.


6
" 그래서 루프 과거 부적절한 하나에서 적절한 입력을 구별 할 (쉬운) 방법이 없습니다. "그 제외하고 한 경우에 모두 eofbitfailbit설정, 다른에만 failbit설정됩니다. 루프가 종료 된 후 모든 반복이 아닌 한 번만 테스트하면 됩니다. 루프를 한 번만 떠나므로 루프를 한 번 떠난 이유 만 확인 하면됩니다. while (in >> data)모든 빈 스트림에 적합합니다.
조나단 Wakely

3
당신이 말하는 (그리고 이전에 한 요점)은 잘못된 형식의 스트림이 !eof & fail과거 루프 로 식별 될 수 있다는 것 입니다. 이것에 의존 할 수없는 경우가 있습니다. 위의 의견을 참조하십시오 ( goo.gl/9mXYX ). 어쨌든, 나는 항상 더 나은 대안 eof으로 검사를 제안하지 않습니다 . 나는 단지 그것이 말하고있는 것입니다 가능성이 일을 (경우에 따라 더 적절한) 방식보다는 "가장 확실하게 잘못!" SO에서 여기에서 주장되는 경향이 있습니다.
교활한

2
"예를 들어, 당신은 데이터를 한 번에 여러 필드를 읽고 오버로드 된 연산자 구조체 >> 인 오류를 검사 할 방법을 고려" 더 단순한 경우 귀하의 포인트를 지원하는 것은 - stream >> my_int: - ""스트림이 예를 들면 포함하는 경우 eofbitfailbit있습니다 세트. 이는 operator>>사용자가 제공 한 과부하가 최소한 사용 eofbit지원을 돕기 위해 돌아 오기 전에 지우는 옵션 이있는 시나리오 보다 나쁩니다 while (s >> x). 더 일반적 으로이 답변은 정리를 사용할 수 있습니다. 결국 만 while( !(in>>ws).eof() )일반적으로 견고하며 끝에 묻혀 있습니다.
Tony Delroy 2012

74

프로그래머가 쓰지 않으면 다음과 같이 쓸 while(stream >> n)수 있습니다.

while(!stream.eof())
{
    stream >> n;
    //some work on n;
}

여기서 문제는 some work on n스트림 읽기가 성공했는지 먼저 확인 하지 않고 수행 할 수 없다는 some work on n것입니다. 실패하면 스트림 결과가 바람직 하지 않기 때문 입니다.

요점은,이다 eofbit, badbit또는 failbit설정 시도가 스트림에서 읽으려고 후. 그래서 경우는 stream >> n실패, 다음 eofbit, badbit또는 failbit당신이 쓰는 경우는 더 관용적 있도록 즉시 설정되어 while (stream >> n)반환 된 객체에 있기 때문에, stream개종자 false가 스트림에서 읽기에 약간의 오류가 있었고 경우 결과적으로 루프가 중지됩니다. 그리고 true읽기에 성공하면 루프가 계속됩니다.


1
의 정의되지 않은 값에 대한 작업을 수행하면서 언급 된 "예기치 않은 결과"외에도 , 실패한 스트림 조작이 입력을 소비하지 않으면 n프로그램이 무한 루프에 빠질 수 있습니다 .
mastov

10

다른 답변은 왜 논리가 틀렸고 while (!stream.eof())어떻게 고치는지를 설명했습니다. 나는 다른 것에 집중하고 싶다 :

eof를 명시 적으로 iostream::eof잘못 사용하는 이유는 무엇입니까?

일반적으로 스트림 추출 ( )이 파일 끝을 누르지 않고 실패 할 수 있기 때문에 점검 eof 잘못되었습니다 >>. 당신은 예를 들어이있는 경우 int n; cin >> n;와 스트림에 포함 된 hello후, h추출, 입력의 끝에 도달없이 실패 할 것이다, 그래서 유효한 숫자가 아닙니다.

이 문제 는 읽기 전에 스트림 상태를 확인하는 일반적인 논리 오류와 결합하여 N 개의 입력 항목에 대해 루프가 N + 1 회 실행됨을 의미하여 다음과 같은 증상을 유발합니다.

  • 스트림이 비어 있으면 루프가 한 번 실행됩니다. >>에 실패하고 (읽을 입력이 없음) 설정 한 모든 변수 stream >> x가 실제로 초기화되지 않았습니다 . 이로 인해 가비지 데이터가 처리되고, 이는 무의미한 결과 (종종 큰 숫자)로 나타날 수 있습니다.

    (표준 라이브러리가 C ++ 11을 준수하는 경우 이제 상황이 조금 다릅니다. 실패 >>하면 숫자 변수를 0초기화되지 않은 상태로 설정합니다 ( chars 제외 ).)

  • 스트림이 비어 있지 않으면, 마지막 유효한 입력 후에 루프가 다시 실행됩니다. 마지막 반복에서 모든 >>작업이 실패 하므로 변수는 이전 반복에서 값을 유지합니다. "마지막 줄이 두 번 인쇄됩니다"또는 "마지막 입력 레코드가 두 번 처리됩니다"로 나타날 수 있습니다.

    (이것은 C ++ 11부터 조금 다르게 나타납니다 (위 참조) : 이제 반복되는 마지막 줄 대신 "0"의 "팬텀 레코드"가 나타납니다.

  • 스트림에 잘못된 형식의 데이터가 포함되어 있지만 확인 .eof만하면 무한 루프가 발생합니다. >>스트림에서 데이터를 추출하지 못하므로 루프는 끝까지 도달하지 않고 제자리에서 회전합니다.


요약하자면, 해결책은 C와 마찬가지로 호출 자체 의 성공을 테스트하는 것처럼 >>별도의 .eof()메소드 를 사용하지 않고 작업 자체 while (stream >> n >> m) { ... }의 성공을 테스트하는 scanfwhile (scanf("%d%d", &n, &m) == 2) { ... }입니다.


1
이것은 가장 정확한 대답입니다. 비록 C ++ 11에서 변수가 더 이상 초기화되지
않았다고
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.