C中的此代码是否属于未定义行为类别?
a
是一个数组, foo
是一个函数, i
是一个int
。
a[++i] = foo(a[i-1], a[i]);
上面的代码会有未定义的行为吗?
数组索引++i
, i-1
和i
保证在数组范围内。
行为是未定义的,但这不是因为在两个序列点之间对同一对象进行了两次修改,但它是UB,因为相对于a[i-1]
和a[i]
的值计算,对i
的副作用是不相关的。用i
§6.5-P(2):
如果对标量对象的副作用相对于对同一标量对象的不同副作用或使用相同标量对象的值进行的值计算未进行排序,则行为未定义 。 如果表达式的子表达式有多个允许的排序,则如果在任何排序中发生这种未测序的副作用,则行为是不确定的。 84)
例如,表达
a[i++] = i;
调用未定义的行为。 同样如此
a[++i] = foo(a[i-1], a[i]);
根据C标准(6.5.16分配运营商):
语义
3 …对操作数的评估是不确定的 。
因此这句话
a[++i] = foo(a[i-1], a[i]);
导致未定义的行为。
是的,这是未定义的行为。
不是因为不允许一个人读取和写入两个序列点之间的相同变量,而是因为规则
在任何两个序列点之间,变量的所有读取应该直接用于计算写入同一变量的结果。
写给i
是i++
。 对同一变量的读取在参数中。 虽然函数调用是一个序列点,但赋值不是。 因此,在评估RHS之前,可以对arrays索引进行评估。
在foo(a[i-1], a[i])
中对i
的读取不直接有助于写入,因此它是UB。
C99标准的相关部分是6.5表达式,§2
在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次。 此外,先前的值应该是只读的,以确定要存储的值。
(强调我的)
行为未定义,因为表达式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在线草案