libc++和libstdc++之间的istream结束符(eof)差异

8
以下(玩具)程序在链接到libstdc++和libc++时返回不同的结果。这是libc++中的一个错误还是我没有理解如何使用istream的eof()函数?我已经尝试在Linux和Mac OS X上使用g++以及在Mac OS X上使用clang,有和没有使用-std=c++0x参数都尝试过。我的印象是,只有在读取失败后(通过get()或其他方式),eof()才会返回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
4个回答

5

编辑:这是由于较旧版本的libc++解释C++标准的方式不同导致的。这个解释在LWG issue 2036中进行了讨论,被认为是不正确的,并进行了更改。

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

旧答案:

您的理解是正确的。

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

  1. 调用good(),如果返回false则设置failbit(这会将一个已经设置了某些其他位的流添加一个failbit),(§27.7.2.1.2[istream::sentry]/2)
  2. 必要时刷新所有绑定的流
  3. 如果此时good()为false,则返回eof并且不执行其他任何操作。
  4. 提取一个字符,就好像通过调用rdbuf()->sbumpc()rdbuf()->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(),并且只有在sbumpc()返回eof时才设置eofbit。

使用最新版本的这两个库,您的测试用例对我产生了相同的输出。


这很奇怪。在我的标准版本(C++03和N3291)中,我找不到你引用的任何文本:我的两个版本都说get“表现为一个未格式化的输入函数。在构造了一个sentry对象之后,如果有字符可用,则提取一个字符c。”没有关于对rdbuf()->sbump()rdbuf()->sgetc()调用次数的说明。虽然我通常不会期望这种情况发生,但是如果实现额外调用rdbuf()->sgetc()并因此设置eofbit,也没有什么不合法的。 - James Kanze
在您的行动清单中有几点需要注意:关于第二点: istream::get() 并不会执行此操作——它是 sentry 对象构造函数的一部分。关于第三和第四点:标准要求较少限制。提取必须 as if 通过调用 rdbuf()->sbumpc()rdbuf()->sgetc()(这是一个错误,因为 rdbuf()->sgetc() 不会提取,而 rdbuf()->snextc()rdbuf->sgetn() 可以提取,但未被提及)。这并没有说明前瞻发生的时间和方式。 - James Kanze
调用streambuf中的任何函数都是可观察行为(因为它们转发到用户定义的虚拟函数,这些函数可能会并且通常会进行系统调用)。所有实现都会在看到文件结束时设置eofbit。如果在特定情况下get()通常不设置它,那是因为可以实现没有任何前瞻的get()。可以,但不必须。但标准对前瞻完全保持沉默。 - James Kanze
@JamesKanze 的 get() 函数被指定为提取一个字符。提取一个字符被指定为类似于 sbumpc/sgetc 的调用。如果返回 eof,则调用 sbumc/sgetc 被指定为导致 eofbit。我同意标准没有说明是对 sbumc/sgetc 的哪个调用,还是包括特别好奇的输入函数添加的不必要调用。 - Cubbi
get()被指定为提取一个字符,就像>> int被指定为提取组成int的字符一样。标准确实允许向前查看;否则,>> int无法实现。如果istream在这个向前查看中遇到EOF,它将设置eofbit;历史上,filebuf的实现不一定会在第二次调用sgetc时返回EOF。eofbit的指定语义是“表示输入操作已达到输入序列的末尾”。如果get()读取了最后一个字节,则为真。 - James Kanze
显示剩余7条评论

4

我应该早点检查其他答案! - Cubbi

1

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
@Cubbi No。 如果sgetc返回eof,但不一定是failbit,则设置eofbit;向前查看始终是合法的,并且有时是必要的。什么时候以及多久get调用sgetc并没有指定。 - James Kanze
我把回复作为答案发布了。 - Cubbi

0

当尝试读取超出文件末尾的操作时,将设置eofbit标志。该操作可能不会失败(例如,如果您正在读取一个整数,并且在整数后没有换行符,则我期望设置eofbit标志,但整数读取仍将成功)。即,我得到并期望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,我并不希望它挂起直到我输入下一行),所以从QOI的角度来看,libstd++似乎是正确的。

对于istream::get的标准描述只是说“提取一个字符c,如果有的话”,而没有描述如何操作,因此似乎无法防止libc++的行为。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接