【问题标题】:istream eof discrepancy between libc++ and libstdc++libc++ 和 libstdc++ 之间的 istream eof 差异
【发布时间】:2012-02-18 18:07:34
【问题描述】:

以下(玩具)程序在链接到 libstdc++ 和 libc++ 时返回不同的内容。这是 libc++ 中的错误还是我不明白 istream eof() 的工作原理?我尝试在 linux 和 mac os x 上使用 g++ 并在 mac os x 上使用 clang 运行它,有和没有 -std=c++0x。我的印象是 eof() 在尝试读取(通过 get() 或其他方式)实际上失败之前不会返回 true。这是 libstdc++ 的行为方式,但不是 libc++ 的行为方式。

#include <iostream>
#include <sstream>

int main() {
    std::stringstream s;

    s << "a";

    std::cout << "EOF? " << (s.eof() ? "T" : "F") << std::endl;
    std::cout << "get: " << s.get() << std::endl;
    std::cout << "EOF? " << (s.eof() ? "T" : "F") << std::endl;

return 0;
}

Thor:~$ g++ test.cpp
Thor:~$ ./a.out
EOF? F
get: 97
EOF? F
Thor:~$ clang++ -std=c++0x -stdlib=libstdc++ test.cpp 
Thor:~$ ./a.out
EOF? F
get: 97
EOF? F
Thor:~$ clang++ -std=c++0x -stdlib=libc++ test.cpp 
Thor:~$ ./a.out
EOF? F
get: 97
EOF? T
Thor:~$ clang++ -stdlib=libc++ test.cpp 
Thor:~$ ./a.out
EOF? F
get: 97
EOF? T

【问题讨论】:

    标签: c++ istream libstdc++ libc++


    【解决方案1】:

    编辑:这是由于旧版本的 libc++ 解释 C++ 标准的方式。解释在LWG issue 2036讨论,判定不正确,改libc++。

    当前的 libc++ 在您的测试中给出与 libstdc++ 相同的结果。

    旧答案:

    你的理解是正确的。

    istream::get() 执行以下操作:

    1. 调用good(),如果它返回false,则设置failbit(这会将故障位添加到设置了其他位的流中),(§27.7.2.1.2[istream::sentry]/2)
    2. 如有必要,刷新任何 tie()'d
    3. 如果此时 good() 为 false,则返回 eof 并且不执行任何其他操作。
    4. 通过调用rdbuf()-&gt;sbumpc()rdbuf()-&gt;sgetc() (§27.7.2.1[istream]/2) 提取字符好像
    5. 如果sbumpc()sgetc() 返回eof,则设置eofbit。 (§27.7.2.1[istream]/3) 和failbit (§27.7.2.2.3[istream.unformatted]/4)
    6. 如果引发异常,则设置 badbit (§27.7.2.2.3[istream.unformatted]/1) 并在允许时重新引发。
    7. 更新 gcount 并返回字符(如果无法获取则返回 eof)。

    (引用自 C++11 的章节,但 C++03 具有所有相同的规则,在 §27.6.* 下)

    现在让我们看一下实现:

    libc++(当前svn版本)将get()的相关部分定义为

    sentry __s(*this, true);
    if (__s)
    {
        __r = this->rdbuf()->sbumpc();
        if (traits_type::eq_int_type(__r, traits_type::eof()))
           this->setstate(ios_base::failbit | ios_base::eofbit);
        else
            __gc_ = 1;
    }
    

    libstdc++(与 gcc 4.6.2 一起提供)定义与

    相同的部分
    sentry __cerb(*this, true);
    if (__cerb)
      {
        __try
          {
            __c = this->rdbuf()->sbumpc();
            // 27.6.1.1 paragraph 3
            if (!traits_type::eq_int_type(__c, __eof))
              _M_gcount = 1;
            else
              __err |= ios_base::eofbit;
          }
    [...]
    if (!_M_gcount)
      __err |= ios_base::failbit;
    

    如您所见,当且仅当 sbumpc() 返回 eof 时,两个库都调用 sbumpc() 并设置 eofbit。

    您的测试用例使用两个库的最新版本为我生成了相同的输出。

    【讨论】:

    • 这很奇怪。我在我的标准版本(C++03 和 N3291)中找不到您引用的任何文本:我的两个版本都说get“表现为未格式化的输入函数。构造一个哨兵对象后,提取一个字符 c,如果有的话。”与rdbuf()-&gt;sbump()rdbuf()-&gt;sgetc() 的通话次数无关。虽然我通常不会想到它,但对 rdbuf()-&gt;sgetc() 进行额外调用并因此设置 eofbit 的实现并没有违法。
    • 您的操作列表中有几点:关于第 2 点:istream::get() 没有这样做——它是 sentry 对象的构造函数的一部分。关于第 3 点和第 4 点:标准的限制要小得多。通过调用rdbuf()-&gt;sbumpc()rdbuf()-&gt;sgetc() 必须如同 进行提取(这是一个错误,因为rdbuf()-&gt;sgetc() 不提取,而rdbuf()-&gt;snextc()rdbuf-&gt;sgetn() 确实如此)没有提到)。这并没有说明何时以及是否发生前瞻。
    • @JamesKanze 关于哨兵,其构造函数的动作是 istream::get() 所做的一部分。就像 C++ 中的任何东西一样,它就好像:实现可能(有时确实)做一些它应该直接在get() 中做的事情。关于对 sgetc 的额外调用——调用它或任何其他函数并没有违法,但设置 eofbit 是非法的,因为它返回的内容会违反 as-if 子句。
    • 调用streambuf 中的任何函数是可观察到的行为(因为它们转发给用户定义的虚拟函数,这些虚拟函数可能并且通常会进行系统调用)。并且所有实现都会在看到文件结尾的任何时候设置eofbit。如果get() 在这种特殊情况下通常不设置它,那是因为可以在没有任何前瞻的情况下实现get()。可以,不是必须。但是该标准对前瞻性保持沉默。
    • @JamesKanze get() 被指定提取一个字符。一个字符的提取被指定作为对 sbumpc/sgetc 的模拟调用。如果返回 eof,则指定调用 sbumc/sgetc 以产生 eofbit。我同意该标准没有说明它是 调用 sbumc/sgetc 还是 任何调用 sbumc/sgetc,包括由特别好奇的输入函数添加的不必要调用.
    【解决方案2】:

    这是一个 libc++ 错误,已按照 Cubbi 的说明进行了修复。我的错。详情在这里:

    http://lwg.github.io/issues/lwg-closed.html#2036

    【讨论】:

    • 我应该早点检查其他答案!
    【解决方案3】:

    s.eof() 的值在第二次调用中未指定——它可能是 真或假,甚至可能不一致。你只能说 如果s.eof() 返回 true,所有未来的输入都将失败(但如果 返回 false,不能保证以后的输入会成功)。 失败后(s.fail()),如果s.eof()返回true,很有可能(但是 不是 100% 确定)失败是由于文件结尾。这是值得的 但是,考虑以下情况:

    double test;
    std::istringstream s1("");
    s1 >> test;
    std::cout << (s1.fail() ? "T" : "F") << (s1.eof() ? "T" : "F") << endl;
    std::istringstream s2("1.e-");
    s2 >> test;
    std::cout << (s2.fail() ? "T" : "F") << (s2.eof() ? "T" : "F") << endl;
    

    在我的机器上,两行都是"TT",尽管第一行 失败是因为没有数据(文件结尾),第二个是因为 浮点值的格式不正确。

    【讨论】:

    • 如何未指定?标准很明确:如果 sbumpc()/sgetc() 返回 eof,则设置 failbit 和 eofbit,如果抛出异常,则设置 badbit。
    • @Cubbi No. 设置eofbit 如果sgetc 返回eof,但不一定是failbit;向前看总是合法的,有时是必要的。并且没有指定get 调用sgetc 的确切时间和频率。
    • 我将回复作为答案发布。
    【解决方案4】:

    当有一个操作试图读取文件末尾时设置eofbit,该操作可能不会失败(如果您正在读取一个整数并且整数之后没有行尾,我希望设置eofbit但整数的读取成功)。 IE。我得到并期待 FT 的

    #include <iostream>
    #include <sstream>
    
    int main() {
        std::stringstream s("12");
        int i;
        s >> i;
    
        std::cout << (s.fail() ? "T" : "F") << (s.eof() ? "T" : "F") << std::endl;
    
        return 0;
    }
    

    在这里,我不希望 istream::get 在返回的字符之后尝试读取(即,如果我用它读取 \n,我不希望它在进入下一行之前挂起),所以 libstd++ 似乎确实是对的,至少在 QOI POV 中是这样。

    istream::get 的标准描述只是说“提取一个字符 c,如果有的话”,而没有描述如何以及似乎并没有阻止 libc++ 行为。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-12
      • 1970-01-01
      • 2017-04-07
      • 1970-01-01
      • 2018-05-10
      • 2013-07-01
      • 2012-12-18
      • 2017-09-25
      相关资源
      最近更新 更多