C ++的istream :: eof()的不一致是规范中的错误还是实现中的错误?

时间:2012-11-02 23:20:33

标签: c++ iostream

以下程序演示了std :: istream(特别是在我的测试代码中,std :: istringstream)设置eof()的方式不一致。

#include <sstream>
#include <cassert>

int main(int argc, const char * argv[])
{
    // EXHIBIT A:
    {
        // An empty stream doesn't recognize that it's empty...
        std::istringstream stream( "" );
        assert( !stream.eof() );        // (Not yet EOF. Maybe should be.)
        // ...until I read from it:
        const int c = stream.get();
        assert( c < 0 );                // (We received garbage.)
        assert( stream.eof() );         // (Now we're EOF.)
    }
    // THE MORAL: EOF only happens when actually attempting to read PAST the end of the stream.

    // EXHIBIT B:
    {
        // A stream that still has data beyond the current read position...
        std::istringstream stream( "c" );
        assert( !stream.eof() );        // (Clearly not yet EOF.)
        // ... clearly isn't eof(). But when I read the last character...
        const int c = stream.get();
        assert( c == 'c' );             // (We received something legit.)
        assert( !stream.eof() );        // (But we're already EOF?! THIS ASSERT FAILS.)
    }
    // THE MORAL: EOF happens when reading the character BEFORE the end of the stream.

    // Conclusion: MADNESS.
    return 0;
}

所以,eof()&#34;火灾&#34;当您在实际的文件结尾之前读取字符时。但是如果流是空的,它只会在您实际尝试读取字符时触发。 eof()是什么意思&#34;你刚试过读完了?&#34;或者&#34;如果你再次尝试阅读,你会走到尽头吗?&#34;答案是不一致的。

此外,断言是否触发取决于编译器。例如,Apple Clang 4.1会触发断言(在读取前面的字符时会引发eof())。例如,GCC 4.7.2没有。

这种不一致性使得很难编写通过流读取但是同时处理空流和非空流的合理循环。

选项1:

while( stream && !stream.eof() )
{
    const int c = stream.get();    // BUG: Wrong if stream was empty before the loop.
    // ...
}

选项2:

while( stream )
{
    const int c = stream.get();
    if( stream.eof() )
    {
        // BUG: Wrong when c in fact got the last character of the stream.
        break;
    }
    // ...
}

所以,朋友们,我如何编写一个循环来解析一个流,依次处理每个字符,处理每个字符,但是当我们点击EOF时,或者在流为空的情况下,不用大惊小怪地停止首先,永远不要开始?

好吧,更深层次的问题:我有直觉,使用peek()可能会解决这个eof()不一致的方式,但是......神圣的废话!为什么不一致?

5 个答案:

答案 0 :(得分:9)

eof()标志仅用于确定您是否在某些操作后点击文件末尾。主要用途是在读取合理失败时避免出现错误消息,因为没有更多要阅读的内容。尝试使用eof()控制循环或某事必然会失败。在所有情况下,您需要检查之后,如果读取成功,则尝试阅读。在尝试之前,流不知道你要读什么。

eof()的语义被彻底定义为“读取流时该标志被设置导致流缓冲区返回失败”。如果我记得正确的话,找到这个陈述并不是那么容易,但这就是下来。在某些时候,标准还表示允许流在某些情况下读取的内容比在某些情况下更多,这可能导致eof()在您不一定期望时设置。一个这样的例子是读取一个字符:流可能最终检测到该字符后面没有任何内容并设置eof()

如果你想处理一个空流,这很简单:从流中查看一些东西,只有当你知道它不是空的时才继续:

if (stream.peek() != std::char_traits<char>::eof()) {
    do_what_needs_to_be_done_for_a_non_empty_stream();
}
else {
    do_something_else();
}

答案 1 :(得分:5)

永远不要单独检查eof

在提取操作期间到达文件结尾时,设置eof标志(与eofbit返回的值中的rdstate()位标志相同)。如果没有提取操作,则永远不会设置eofbit,这就是您的第一次检查返回false的原因。

但是eofbit并不表示操作是否成功。为此,请检查failbit|badbit中的rdstate()failbit表示“存在逻辑错误”,badbit表示“存在I / O错误”。方便的是,有fail()函数可以准确返回rdstate() & (failbit|badbit)。更方便的是,有operator bool()函数返回!fail()。所以你可以做while(stream.read(buffer)){ ...

之类的事情

如果操作失败,您可以分别检查eofbitbadbitfailbit以找出 失败的原因。

答案 2 :(得分:1)

您使用的是什么编译器/标准c ++库?我用gcc 4.6.3 / 4.7.2和clang 3.1试了一下,所有这些都工作正常(即断言不会触发)。

我认为您应该将此报告为工具链中的错误,因为我对标准的阅读符合您的直觉,即只要get()能够返回一个字符,就不应该设置eof()。 / p>

答案 3 :(得分:1)

这不是一个错误,因为它是预期的行为。它是 在输入之前使用eof()测试的意图 失败。它的主要目的是用于内部提取功能,其中 在早期实施中,std::streambuf::sgetc()的事实 返回EOF并不意味着它会在下次被调用时: 意图是随时sgetc()返回EOF(现在 std::char_traits<>::eof(),这将被记住,以及流 不再打电话给streambuf。

实际上说:我们确实需要两个eof():一个供内部使用, 如上所述,另一个将可靠地说明失败是由于 已达到文件结尾。事实上,给出了类似的东西:

std::istringstream s( "1.23e+" );
s >> aDouble;

无法检测到错误是由格式错误引起的, 而不是流没有任何更多的数据。在这种情况下, 内部eof应该返回true(因为我们已经看到文件结束,何时 展望未来,我们希望抑制所有进一步的呼叫 streambuf提取器函数),但外部函数应为false, 因为存在数据(即使在跳过初始空格后)。

如果你没有实现一个提取器功能,你应该这样做 在您实际输入失败之前,请不要测试ios_base::eof()。 从来没有意图提供任何有用的信息 (这让他们想知道为什么他们定义ios_base::good() - 事实上,如果false意味着它可以提供,则返回eof() fail()true的可靠信息返回false,此时,我们 知道它会返回{{1}},所以没有必要调用它。)

而且我不确定你的问题是什么。因为流不能知道 提前你的下一个输入是什么(例如它是否会跳过 是不是空格,它不能提前知道你的下一个输入 会因为文件结束而失败。采用的习语很明确: 尝试输入,然后测试是否成功。没有 另一方面,因为没有其他替代方案可以实施。帕斯卡尔 它有所不同,但Pascal中的文件被输入 - 你只能阅读 一个类型,所以它总是可以读取前面的一个元素 引擎盖,如果此预读失败则返回文件末尾。没有 previsional文件结束是我们为能够阅读更多而付出的代价 比文件中的一种类型。

答案 4 :(得分:0)

这种行为有点微妙。当尝试读取超过文件末尾时设置eofbit,但这可能会也可能不会导致当前提取操作失败。

例如:

ifstream blah;
// assume the file got opened
int i, j;
blah >> i;
if (!blah.eof())
    blah >> j;

如果文件包含142<EOF>,则数字序列在文件结尾处终止,因此设置eofbit并且提取成功。未尝试提取j,因为已经遇到文件结尾。

如果文件包含142 <EOF>,则数字序列由空格终止(i提取成功)。 eofbit尚未设置,因此blah >> j将被执行,并且它将在没有找到任何数字的情况下到达文件末尾,因此它将失败。

注意文件末尾看起来无害的空格是如何改变行为的。