【问题标题】:Undefined behavior with std::movestd::move 的未定义行为
【发布时间】:2015-11-27 13:08:57
【问题描述】:

来自 cppreference 的move page

除非另有说明,否则所有具有 被移出的被置于有效但未指定的状态。那是, 只有没有前置条件的函数,比如赋值 运算符,可以在对象被移动后安全地用于对象

因此,从同一页面上的示例来看,下面的代码被认为是未定义的行为

vector<string> v_string;
string example = "example";
v_string.push_back(move(example));
cout << example << endl;

MSVC 不会在控制台上输出任何内容,但如果我这样做了

vector<int> v_int;
int number = 10;
v_int.push_back(move(number));
cout << number << endl;

将输出 10。发生这种情况有什么原因吗?还是它总是未定义的行为?

【问题讨论】:

  • 我认为“有效状态”是指保持对象不变量。因此,例如,将其流式传输到标准输出应该没问题,但您无法安全地访问example[0]。我认为你的例子都没有显示 UB。
  • unspecified != undefined,你可以打印字符串,但不能保证里面有什么。
  • 嗯,在我看到的所有实现中,STL 容器和字符串在移动后都会变为空。
  • 一个字符串被移动后,不知道它包含什么。但是它会在内部处于一个定义良好(如果你不知道)的状态。所以将它发送到std::cout 不会破坏程序的稳定性。但它可以包含它在移动之前包含的所有内容,或者它可能是空的。通常你不会移动一个左值,除非你没有进一步使用它。如果你这样做了,那么你的下一个动作应该是重新初始化它。
  • 对于标量移动与复制相同,因此int 保持不变。对于您的string 示例,这取决于它的operator&lt;&lt; 是否有任何先决条件,而IIRC 没有。

标签: c++ c++11


【解决方案1】:

那是因为 string 可以通过窃取指向实际字符的指针来有效地从 moved 中提取,这就是编译器所做的,而将 string 中的 moved 保留为“空”。 int 不能有效地moved,您只需复制一份即可。因此旧的int 仍然存在。但这是不记录的。只是不要使用您moveed 的对象,也不要依赖未指定的行为。

【讨论】:

  • 我认为编译器没有任何权限将随机值分配给移动的整数。 std::move 只是将其参数转换为一个 xvalue。这根本不会改变论点。
【解决方案2】:

未定义的行为只是意味着

本国际标准没有要求的行为。 允许的未定义行为范围从忽略情况 完全具有不可预测的结果,在翻译过程中表现得很好 或以文件化方式执行程序的特征 环境(无论是否发布诊断消息),以 终止翻译或执行(发出 诊断信息)。许多错误的程序结构不会产生 未定义的行为;他们需要被诊断出来。

如果您有兴趣,请查看"What every C programmer should know about undefined behavior"。真是让人大开眼界。

在您的情况下,它不是未定义的行为,而是未指定的状态: 在 21.4.2.16 中,C++ 标准定义了移动构造函数的语义:

构造一个 basic_string 类的对象,如表 69 所示。 存储的分配器是从 alloc 构造的。在第二种形式中,str 处于未指定值的有效状态。

“第二种形式”是移动构造函数,因此字符串处于未指定状态。

这意味着对象必须处于满足不变量的状态,但未指定其他任何内容。对于字符串,任何内容都可以。

【讨论】:

  • 很确定这里没有未定义的行为,但该链接仍然值得一读。
  • @nwp 我实际上在定义语义的标准中找到了一个参考。字符串处于未指定状态。我会将其视为某些未定义行为的情况,尽管它不是一般的 UB。
  • @Jens:不,这不是未定义行为的情况,甚至不是某些情况。听起来可能很挑剔,但区别很重要:没有良性未定义行为,任何包含未定义行为的程序都会被破坏。相反,未指定的状态是完全可以的,只要你不依赖任何特定的状态。
【解决方案3】:

未指定并不意味着未定义。

根据 C++11 标准,第 17.3.26 节:

有效但未指定的状态 未指定的对象状态,除非满足对象的不变量并且对对象的操作按照其类型指定的行为表现

由于对象处于有效状态,您可以将其流式传输到输出,因为流式传输没有额外的先决条件。然而,what 是未指定的,因此它可能什么也不打印,或者打印出你父亲闻起来有接骨木浆果的味道。您不能安全地使用带有附加前提条件的函数,例如back(),它还要求字符串非空。有效字符串可以为空。

对于未指定但有效的状态,包含旧值是完全可以接受的选项。对于 int 等基本类型,简单的复制只是执行移动的最有效方式。

还应注意int 不是标准库对象,而是基本类型(如第 3.9.1 节中所定义)。因此您的报价不适用。

【讨论】:

  • 作为参考,以下是标准中的定义:“除了满足对象的不变量并且对象上的操作按照其类型指定的行为外,未指定的对象状态”
  • @Jens 谢谢,添加了相应的标准报价
猜你喜欢
  • 2014-02-16
  • 2019-07-20
  • 2023-02-07
  • 2016-10-03
  • 2011-07-28
  • 1970-01-01
  • 2018-10-18
  • 2021-03-15
相关资源
最近更新 更多