【问题标题】:Does this code in C fall into the Undefined Behavior category?C 中的这段代码是否属于未定义行为类别?
【发布时间】:2018-01-29 21:16:25
【问题描述】:

a 是一个数组,foo 是一个函数,i 是一个 int

a[++i] = foo(a[i-1], a[i]);

上面的代码会有未定义的行为吗?

数组索引++ii-1i,保证在数组范围内。

【问题讨论】:

  • 是什么让您认为它可能具有未定义的行为?
  • @litelite 您认为序列点在哪里?执行顺序是什么?
  • @PeterJ 序列点?
  • @litelite 是评估 ++ 的点。在 foo 之前还是之后?
  • 这实际上很棘手,因为函数调用 序列点(以及从库函数返回)。它在这里没有帮助,因为分配是无序的,所以你不知道函数调用何时发生相对于在a[++i] 中递增i

标签: c arrays language-lawyer increment undefined-behavior


【解决方案1】:

行为未定义,但不是因为在两个序列点之间对同一对象进行了两次修改,而是因为对i 的副作用相对于a[i-1] 和@987654323 的值计算是未排序的@使用i

§6.5-p(2):

如果标量对象的副作用相对于同一标量对象的不同副作用或使用同一标量对象的值的值计算是无序的,则行为未定义。如果一个表达式的子表达式有多个允许的排序,则如果在任何排序中出现这种未排序的副作用,则行为未定义。84)

例如表达式

a[i++] = i;

调用未定义的行为。

也是如此
a[++i] = foo(a[i-1], a[i]);

【讨论】:

  • 很好的简化示例。阐明了对稍微复杂的语句的相同规则的应用。
  • 这个呢:i++;a[i] = foo(a[i-2], a[i-1]);?
  • @BiteBytes;这是执行此操作的正确方法并已定义行为。
【解决方案2】:

根据 C 标准(6.5.16 赋值运算符):

语义

3 ...操作数的计算是无序的

所以这个说法

a[++i] = foo(a[i-1], a[i]);

导致未定义的行为。

【讨论】:

  • 仅仅评估未排序的操作数并不意味着它是 UB。每个函数调用的参数评估都是无序的。
  • @AjayBrahmakshatriya 在所示示例中,这是未定义行为的原因。
  • @VladfromMoscow;您在显示的示例中是对的,“未排序”是 UB 的原因,但不是唯一的原因,以及对 i 的修改。这就是 Ajay 想说的。
  • @hacks 很清楚,没有任何评论。这就是问题的原因。
  • 更具体地说,这不是关于评估顺序,而是关于副作用。
【解决方案3】:

是的,这是未定义的行为。

不是因为不允许在两个序列点之间读取和写入相同的变量,而是因为规则

在任意两个序列点之间,一个变量的所有读取都应该直接用于计算写入同一变量的结果。

这里写给i的是i++。对同一变量的读取位于参数中。 尽管函数调用是一个序列点,但赋值不是。因此,数组索引的评估可以在 RHS 评估之前进行。

foo(a[i-1], a[i]) 中的 i 上的读取不会直接影响写入,因此它是 UB。

C99 标准的相关部分是 6.5 Expressions, §2

在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的评估修改一次。 此外,先读取的值应只读以确定要存储的值。

(强调我的)

【讨论】:

【解决方案4】:

该行为未定义,因为表达式 a[++i]a[i-1]a[i] 相对于彼此没有排序。

章节和诗句:

6.5 表达式
...
2     如果标量对象的副作用相对于不同的副作用没有排序 在同一标量对象或使用同一标量的值的值计算上 对象,行为未定义。 如果有多个允许的顺序 表达式的子表达式,如果这样一个未排序的一面,则行为未定义 效果发生在任何排序中。84)
...
6.5.2.2 函数调用
...
10     在函数指示符和实际值的评估之后有一个序列点 参数,但在实际调用之前。调用函数中的每个评估(包括 其他函数调用)在之前或之后没有特别排序的 被调用函数体的执行顺序是不确定的 被调用函数的执行。94)
...
6.5.16 赋值运算符
...
3     赋值运算符将值存储在左操作数指定的对象中。一个 赋值表达式具有赋值后左操作数的值,111) 但不是 一个左值。赋值表达式的类型是左操作数的类型 左值转换后。更新左操作数的存储值的副作用是 在左右操作数的值计算之后排序。 评价 操作数未排序。
84) 本段呈现未定义的语句表达式,例如
    i = ++i + 1;
    a[i++] = i;
同时允许
    i = i + 1;
    a[i] = i;
...
94) 换句话说,函数执行不会相互“交错”
...
111)允许实现读取对象以确定值,但不是必需的,即使 当对象具有 volatile 限定类型时。

C 2011 Online Draft

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-04
    • 1970-01-01
    • 1970-01-01
    • 2011-08-25
    • 2015-05-26
    • 1970-01-01
    • 2015-02-16
    • 1970-01-01
    相关资源
    最近更新 更多