【问题标题】:Is std::cin >> i >> ++i; undefined behavior是 std::cin >> i >> ++i;未定义的行为
【发布时间】:2021-12-27 17:37:18
【问题描述】:

我试图了解 C++ 中表达式的求值顺序。我有以下例子:

int i = 0;
std::cin >> i >> i; //IS THIS UB? 

我的第一个问题是上面显示的 sn-p 会产生 UB 吗?

接下来,

int i = 0;
std::cin >> i >> ++i; //IS THIS UB?

我的第二个问题是,这个 sn-p 会产生 UB 吗?

【问题讨论】:

  • 我真的很好奇std::cin >> i >> ++i;的用例增加一个变量,然后立即覆盖该值。你为什么要这样做?
  • 我正在学习表达式的评估,所以只是想知道这是否会导致 UB。例如,我认为std::cout << i << ++i; 会导致 UB,但为什么不是std::cin >> i >> ++i;
  • 如果您只是在顺序无关紧要的地方编写代码,您实际上不必知道所有细节。并指定重要的顺序(使用单独的语句)。例如,我认为 std::cin >> i >> i; 在技术上是可以的,但仍将其写为 std::cin >> ignored >> i; 以表明我真的只想要第二个值。
  • 我的问题是你为什么要编写难以理解的代码。在任何专业环境中,您都会因这种糟糕的风格而受到指责,并让您更加明确地表达出来。
  • @BoP:std::cin >> a[i] >> a[++i]; 怎么样。

标签: c++ undefined-behavior operator-precedence evaluation


【解决方案1】:

std::cin >> i >> i; 是并且一直是明确定义的。

std::cin >> i >> ++i; 现在定义良好,但在 C++17 之前是 UB。

这种 UB 是 caused either by 对同一标量的未排序写入,或相对于同一标量的读取未排序的写入。

std::cin >> i >> i; 中,即使i 被修改了两次(在重载的>> 内),第一次修改在第二次修改之前排序。必须首先计算第一个 >> 的返回值,作为参数传递给第二个 >>,因此必须按此顺序调用它们。 See rule (3) here.

另一方面,在 std::cin >> i >> ++i; 中,C++17 之前的第一个 >>i 的修改相对于 ++i 是无序的,从而导致 UB。但在 C++17 及更高版本中,>><< 总是在 rhs 之前评估 lhs,导致第一个 >>++isee rule (19) here 之前排序。

【讨论】:

  • 即使i 被修改了两次 这不是真的,i 可以保持不变。
  • @MartinYork 谢谢,现在应该更好了。
  • @S.M.是的,但这不应该影响明确的定义吗?请注意,OP 将其归零。
  • 是的,这不应该影响明确定义。结果中未指定变量值,可能是 1,或者递增的第一个输入,或者第二个输入。
【解决方案2】:

案例一

在这种情况下,std::cin >> i >> i; 可以写成(或等价于):

std::cin.operator>>(i).operator>>(i);

这里有两件重要的事情需要考虑

  1. std::cin::operator>> 通过引用返回 cin 对象
  2. std::cin>>operator>> 的每次调用都保证按照从左到右的顺序进行。

所以在这种情况下没有未定义的行为。

案例二

在这种情况下,std::cin >> i >> ++i; 可以写成(或等价于):

std::cin.operator>>(i).operator>>(++i);

现在,来自Section 1.9, Point 16

当调用一个函数时(无论该函数是否是内联的),在函数体中的任何表达式或语句执行之前,在所有函数参数(如果有的话)的求值之后都会有一个序列点。在复制返回值之后和函数外的任何表达式执行之前,还有一个序列点。

但请注意,上述引用声明仅保证:

  1. i 在第一次调用 operator>> 之前进行评估,并且
  2. ++i 在第二次调用 operator>> 之前进行评估。

但引用不保证i++i 之前被评估,反之亦然。这意味着i++i 的评估顺序是undefined,因此在这种情况下生成的代码将具有undefined behavior

以上讨论(序列点)适用于Pre-C++11,因为OP没有标记C++11。

【讨论】:

  • 在 C++11 中删除了序列点,您引用了一些旧标准。
  • @HolyBlackCat OP 没有标记C++11 所以我给出了相应的答案。
  • @HolyBlackCat 在我的答案末尾添加了一条注释。看看吧。
  • @AnoopRana C++ 不是 C++98,C++ 是 current ISO 标准指定的 C++。这不需要特别标记。 OTOH C++98 问题确实需要标记。它们不是关于 C++,它们是关于 C++ 的一个历史版本,它在今天几乎不相关。
  • @n.1.8e9-where's-my-sharem。无论如何,我已经在答案的末尾提到了,以便那些尚未意识到这是 C++11 之前的人(未来的读者)会知道(意识到这一事实)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-15
  • 2011-05-19
  • 2011-04-25
  • 1970-01-01
相关资源
最近更新 更多