【问题标题】:Why is one of these examples undefined behavior and the other is not?为什么这些示例之一是未定义的行为而另一个不是?
【发布时间】:2019-09-09 06:26:04
【问题描述】:

this 回复的评论指出,不应使用以下代码,因为它表现出未定义的行为:

int old = (std::cin >> old, old);

here 的类似代码也遭到了极大的蔑视,尤其是因为它表现出未定义的行为。

另一方面,this 高度赞成的回复建议以下代码作为逗号运算符有用性的示例:

while (cin >> str, str != "STOP") {
    //process str
}

我假设如果这段代码表现出未定义的行为,它就不会被投票。

问题:如果第一个代码是未定义的行为(可能是因为使用从cin读取的结果而没有检查后者的状态),那么为什么第二个代码可以?

编辑:第一个例子的评论部分回答了这个问题。第二个示例没有显示strstd::string 的一个实例,因此被初始化。因此,没有未定义的行为。

【问题讨论】:

  • @6502 好像已经在其他问题的评论区回答了你?
  • 这也很大程度上取决于用于构建应用程序的 C++ 标准。参见例如this old question.
  • 很多关于这个问题的神话突然出现 - 是的,在某处可能有一个很好的重复,但我认为值得提交一个简短的“总结”答案。
  • 您链接的第一个问题没有答案,因此没有理由声称代码具有未定义的行为。评论不能作为答案。

标签: c++ undefined-behavior


【解决方案1】:

声明 int old = (std::cin >> old, old); 自 C++11 以来定义良好假设读取非空白字符。这是因为如果std::cin 在这种情况下失败,old 将设置为零。 表达式分隔符运算符,的使用也是合法的,因为它对表达式std::cin >> oldold进行排序。如果没有反空白字符,则 old 仍然不会被 std::cin >> old 更改,并且代码的行为是未定义的。

假设strstd::string 类型,(cin >> str, str != "STOP") 总是 定义明确。如果cin >> str 失败,则保留str 的初始(可能是默认构造)值,然后, 再次对表达式进行排序。

【讨论】:

  • "如果 std::cin 失败,旧设置为零。" 不正确。如果读取了非空白字符但无法转换为 int,则将其设置为零。如果无法提取非空白字符,则保持不变。
  • “提取”直到哨兵对象成功完成后才开始。从您的页面中看到“1-4) 表现为 FormattedInputFunction。在构造和检查哨兵对象(可能会跳过前导空格)之后,通过调用 std:提取一个整数值: :num_get::get()”。您可以查看标准(istream.formatted)作为更详细和权威的参考。
  • @M.M 我稍后会回顾。现在得去开会了。
【解决方案2】:

两个答案都是错误的答案,但只有第一个可能表现出未定义的行为。

int old = (std::cin >> old, old);

std::cin >> old 失败时,old 可以拥有它所拥有的价值。所以它可以保持未初始化的int,因此读取它是未定义的行为。

while (cin >> str, str != "STOP") {
    //process str
}

cin >> str 失败时,str 具有其先前的值。假设 strstd::string 与聚合类型不同,它不可能有未初始化的状态。因此它将具有其先前的值,例如默认构造值"" 或先前读取的值"MOP"。从中读取并与"STOP" 进行比较绝不是未定义的行为。然而,cin >> str 很可能会在下一个循环中再次失败,因此 str 永远不会是 "STOP",因此它将是无限循环,因此 && is likely better than comma there

另外值得注意的是,当我们尝试结合第一个示例和第二个示例时,我们can get real crash

std::string old = (std::cin >> old, old);

当流未设置为引发故障时,我们应该手动检查它们的状态(std::cin 并不特殊),因为它们可能会结束并失败。不幸的是,很多教程示例都没有这样做。因此,有些人似乎认为流操作适合使用逗号运算符插入到某些表达式的中间。

编辑:

我不确定它与 C++11 不同的评论。挖了,但在标准中找不到确认。使用 g++ 尝试此代码在出现错误时未读取零:

#include <iostream>
#include <sstream>

int main()
{
    std::stringstream in("42");

    int i = 0;
    std::cout << i << '\n'; // outputs 0
    in >> i;
    std::cout << i << '\n'; // outputs 42
    i = 666;
    in >> i;
    std::cout << i << '\n'; // outputs 666 
    // so it did leave value of i like it was 
}

Demo。还有std::cin does not seem special 以任何方式。当然,可能是 g++ 的实现不正确,或者仅在失败子集的情况下才需要失败的输入来设置 i,因此第一个示例仍然可能表现出未定义的行为。

【讨论】:

  • 从 C++11 开始,第一个例子不会是 UB,因为即使输入操作失败,old 也会被初始化。请参阅我的this old answer
  • @Someprogrammerdude 这在cplusplus.com/reference/istream/istream/operator>> 中没有提到。能否请您引用标准的引用来回复?
  • @AlwaysLearning 老实说,cplusplus.com 并不是一个很好的参考网站,因为它们并不总是完全更新,而且过去实际上是错误的。在我的回答中,我有指向 cppreference.com 的链接,该链接非常最新,并且非常严格地遵循标准。我真的没有时间在标准中找到相关部分,但草案是免费提供的(参见例如here for links)。
  • @Someprogrammerdude 我做了实验,似乎失败的输入至少在 g++ 9.2.0 中不会修改值。
猜你喜欢
  • 2013-09-13
  • 2021-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-26
  • 2011-05-05
  • 1970-01-01
相关资源
最近更新 更多