【问题标题】:c - Why does i = ++i invoke undefined behaviour? [duplicate]c - 为什么 i = ++i 调用未定义的行为? [复制]
【发布时间】:2015-01-09 18:00:04
【问题描述】:

我了解 C 使用序列点的概念来识别模棱两可的计算,并且 = 运算符不是序列点。但是,我在执行该语句时看不到任何歧义

i = ++i

根据我的理解,这仅相当于评估 &i 处的任何内容,将其递增,然后将其存储回同一位置。然而,GCC 将其标记为:

[警告] 'i' 上的操作可能是未定义的 [-Wsequence-point]

我是否遗漏了 = 的功能?

编辑:在标记为重复之前,请注意我已经浏览过其他关于序列点和未定义行为的帖子。它们都没有专门解决表达式i=++i(注意pre-increment)。提到的表达式通常是i=i++a=b++ + ++b 等。我对它们中的任何一个都没有疑问。

【问题讨论】:

  • 我认为您遗漏了一些关于 ++ 的副作用如何发挥作用的内容。
  • 返回的结果将是i的递增值。但是i 可能会在分配后递增。该标准规定副作用将在下一个序列点之前发生,但可能在分配之前或之后。
  • 您的困惑可能是因为您不了解序列点。请参阅here 并查看此问题:stackoverflow.com/questions/4176328/…
  • 不,你误会了。我并不是说++i 返回的值可能不同。我是说它总是会返回i+1。但是,增量副作用可能发生在分配之前或之后。副作用发生在 `a = ++b;'没有区别,因为没有自我分配。
  • @FredLarson 所以你是说返回 i+1 的值和修改 &i 的值是独立事件吗?所以返回计算出来的值才是真正的操作,在i的内存位置增加值是使用++操作符的副作用吗?

标签: c increment undefined-behavior operator-precedence sequence-points


【解决方案1】:

您遗漏了一些关于未定义行为的信息。未定义的行为仅仅意味着编译器可以为所欲为。它可以抛出一个错误,它可以(就像 GCC 一样)显示一个警告,它可以让恶魔从你的鼻子里飞出来。最主要的是,它的行为不会很好,而且在编译器之间的行为也不会一致,所以不要这样做!

在这种情况下,编译器不必保证运算符的 lhs 的副作用必须在语句的 rhs 返回之前完成。这对您来说似乎很有趣,但您不像计算机那样思考。如果需要,它可以计算返回值并将其返回到寄存器中,将其分配给 i,然后对实际值执行增量。所以它看起来更像

register=i+1;
i=register;
i=i+1;

标准不保证不会发生这种情况,所以不要这样做!

【讨论】:

  • 问题不是什么是UB,问题是为什么是UB?
  • @didierc 已编辑以回答该问题。
  • @IdeaHat 增量将在其值分配给 lhs 之前完成。
  • 很好的答案,但在我看来,副作用严格意味着更新内存单元。同时进行增量也在执行计算(恕我直言)。我认为混乱来自那个确切的点。
  • 是的:副作用是“更新内存单元”。但是标准中没有定义/规定“更新存储单元”的方法;任何具有相同效果的东西都将被允许完成它(大多数实现需要将值提取到寄存器中,增加寄存器中的值并将其写回“单元格”并且“写回”的时刻是不固定;唯一的要求是它应该在下一个序列点(;)之前完成,这就是禁止在序列点之间多次修改值的原因(或导致 UB)
【解决方案2】:

出现未定义行为是因为变量i 在两个sequence points 之间被多次修改。序列点是之前评估的所有副作用可见,但没有未来副作用可见的点。标准规定:

在前一个和下一个序列点之间,一个对象应该有 它的存储值最多修改一次由评估 表达。此外,先验值应仅读取到 确定要存储的值。

那么,我们关心的副作用是什么?

  • ++i将值分配给 i i+1
  • i = ++i将表达式 ++i 的值赋给 i,即 i+1

所以,我们将得到两个(诚然,等效的)副作用:将i+1 分配给变量i。我们关心的是,这些副作用发生在哪两个序列点之间?

哪些操作构成序列点?有多个,但这里只有一个真正相关:

  • 完整表达式的末尾(在这种情况下,i = ++i 是完整表达式)

即前置增量++i不是序列点。这意味着 both 副作用(增量和赋值)将发生在相同的两个序列点之间,修改相同的变量 i。因此,这是未定义的行为;两个修改碰巧具有相同的值这一事实是无关紧要的。


但是为什么在序列点之间多次修改一个变量是不好的呢?为了防止出现以下情况:

i = ++i + 1;

这里,i 增加了,但由于预增量的语义,它也被分配了值(i+1) + 1。由于副作用的顺序不明确,因此行为未定义。

现在,假设标准中有一个特殊情况,即两个序列点之间的多次修改是可以的,只要值相同,但这可能会不必要地使编译器实现复杂化,没有太多好处。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-15
    • 1970-01-01
    • 2011-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多