【问题标题】:Why does std::basic_istream::ignore() extract more characters than specified?为什么 std::basic_istream::ignore() 提取的字符比指定的多?
【发布时间】:2020-10-05 07:49:01
【问题描述】:

我有以下代码:

#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main(int argc, char* argv[]) {
    stringstream buffer("1234567890 ");
    cout << "pos-before: " << buffer.tellg() << endl;
    buffer.ignore(10, ' ');
    cout << "pos-after: " << buffer.tellg() << endl;
    cout << "eof: " << buffer.eof() << endl;
}

它会产生这个输出:

pos-before: 0
pos-after: 11
eof: 0

我希望 pos-after10 而不是 11。根据规范,当满足以下任一条件时,ignore 方法应该停止:

  1. 个字符已提取。在 count 等于 std::numeric_limits&lt;std::streamsize&gt;::max() 的特殊情况下禁用此测试
  2. 文件结束条件出现在输入序列中,在这种情况下,函数调用 setstate(eofbit)
  3. 输入序列中的下一个可用字符 c 是 delim,由 Traits::eq_int_type(Traits::to_int_type(c), delim) 确定。分隔符被提取并丢弃。如果 delim 为 Traits::eof(),则禁用此测试

在这种情况下,我希望规则 1 在所有其他规则之前触发,并在流位置为 10 时停止。

Execution 表明情况并非如此。我误会了什么?

我还尝试了只忽略 9 个字符的代码变体。在这种情况下,输出是预期的:

pos-before: 0
pos-after: 9
eof: 0

所以看起来在ignore()提取字符数的情况下,它仍然检查下一个字符是否是delimiter,如果是,它也提取它。 我可以使用g++clang++ 进行复制。

我也尝试了这种代码变体:

#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main(int argc, char* argv[]) {
    cout << "--- 10x get\n";
    stringstream buffer("1234567890");
    cout << "pos-before: " << buffer.tellg() << '\n';
    for(int i=0; i<10; ++i)
        buffer.get();
    cout << "pos-after: " << buffer.tellg() << '\n';
    cout << "eof: " << buffer.eof() << '\n';
    
    cout << "--- ignore(10)\n";
    stringstream buffer2("1234567890");
    cout << "pos-before: " << buffer2.tellg() << '\n';
    buffer2.ignore(10);
    cout << "pos-after: " << buffer2.tellg() << '\n';
    cout << "eof: " << buffer2.eof() << '\n';
}

结果是:

--- 10x get
pos-before: 0
pos-after: 10
eof: 0
--- ignore(10)
pos-before: 0
pos-after: -1
eof: 1

我们看到使用ignore() 会在文件上产生文件结束条件。表明ignore() 确实尝试在提取 10 个字符之后 提取一个字符。但在这种情况下,第 3 个条件被禁用,ignore() 不应该尝试查看下一个字符是什么。

【问题讨论】:

  • 足够有趣的 Clang 10.0 打印 11 但 Clang 主干打印 10 (godbolt.org/z/ErKqon) 。 MSVC 还打印 10(本地测试)。
  • 谢谢,虽然它不可能是一个错误,因为 clang 和 gcc 同意(在我的机器上):)
  • 这是一个标准库错误,而不是编译器错误。除非特别说明,否则 Clang 通常使用与 gcc 相同的标准库。

标签: c++ iostream


【解决方案1】:

[istream.unformatted] 第 25 段中std::basic_istream::ignore 的规范有点不清楚:它声明“提取字符直到发生以下任何情况:”没有任何顺序指示。第 25.1 段规定最多提取 n 字符(除非 nstd::numeric_limits&lt;std::streamsize&gt;)并且第 25.3 段规定字符匹配。但是,即使条件可以按任何顺序应用,这里也不会有冲突:nth 字符还不是预期的字符,ignore() 应该停止。

正如评论中所指出的,libstdc++ 中有一个bug,它似乎仍然存在于与gcc-10.2.0 一起提供的库中。将clang++libc++ 一起使用(如有必要,在调用clang++ 时使用-stdlib=libc++)不会显示相同的行为。

顺便说一句:未格式化的输入操作正在设置读取的字符数,可以使用gcount() 访问。在流中查找是一种比访问此计数更昂贵的操作。使用gcount() 也显示了问题(说到昂贵的操作,我还使用'\n' 替换了std::endl 的使用;有关更多详细信息,请参阅this videothis article):

#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::istringstream buffer("1234567890 ");
    buffer.ignore(10, ' ');
    std::cout << "gcount: " << buffer.gcount() << '\n';
    std::cout << "eof: " << std::boolalpha << buffer.eof() << '\n';
}

【讨论】:

  • 谢谢。打开的错误看起来与我的第一个用例相似。然而我相信@chris-dodd 的解释是有效的,它与(在我的理解中)这个错误是有效的相矛盾。我想我只需要远离ignore(),直到标准明确(或 iostream 已过时)。
【解决方案2】:

cppreference 是臭名昭著的——你通常不应该依赖它来处理语言中的极端情况,而是参考规范,它说:

效果:表现为未格式化的输入函数(如上所述)。建立哨兵后 对象,提取字符并丢弃它们。提取字符直到以下任何一项 发生:

  • n != numeric_limits::max() (18.3.2) 和 n 个字符已被提取,所以 远
  • 文件结束出现在输入序列上(在这种情况下,函数调用 setstate(eofbit), 这可能会抛出 ios_base::failure (27.5.5.4));
  • traits::eq_int_type(traits::to_int_type(c), delim) 用于下一个可用输入字符 c(在这种情况下 c 被提取)。

在这里使用“any of”而不是“one of”可以清楚地表明ignore 将在多个条件适用时停止。这就是这里的根本问题——第一个和第三个条件都适用,这会带来一个未指定的极端情况——第三个条件表明下一个可用字符(与分隔符匹配)也将被提取。

所以这正是库在这种情况下所做的——第三个条件适用,所以它提取了字符。第一个条件也适用这一事实无关紧要。

【讨论】:

  • 我真的不明白 cppreference 有什么问题(就此而言,它不是我通常听说的因错误而臭名昭著的错误;那就是 cplusplus.com)。 cppreference 语言在规范中添加了一个不必要的“一个”,但在最坏的情况下,它是该语言的轻微不精确,并且它并不意味着规则的应用有顺序(这是这里真正的问题;甚至在语言规范中,当 1 和 3 都为真时,应该会发生什么,这是不必要的混淆(提取分隔符可能更清楚)。
  • 谢谢你。这解释了我的第一个测试用例,但不是第二个测试用例,其中第 11 个字符是 eof 并且根据规范不应该提取第 11 个字符。
  • 另外,如果我理解正确你所说的,似乎错误:gcc.gnu.org/bugzilla/show_bug.cgi?id=94749 不是错误,但它被接受了。
猜你喜欢
  • 1970-01-01
  • 2014-05-24
  • 2022-09-28
  • 2017-03-11
  • 1970-01-01
  • 2019-07-22
  • 2015-01-07
  • 1970-01-01
  • 2020-10-17
相关资源
最近更新 更多