5

我一直认为,在读取赋值中的正确表达式后缺少序列点会使如下示例产生未定义的行为:

void f(void)
{
   int *p;
   /*...*/
   p = (int [2]){*p};
   /*...*/
}
// p is assigned the address of the first element of an array of two ints, the
// first having the value previously pointed to by p and the second, zero. The
// expressions in this compound literal need not be constant. The unnamed object
// has automatic storage duration.

但是,这是 C11 标准委员会草案中“6.5.2.5 复合文字”下的示例 2,版本标识为 n1570,我理解为最终草案(我无权访问最终版本)。

所以,我的问题是:标准中有什么东西可以给出这种定义和指定的行为吗?

编辑

我想准确地解释一下我所看到的问题,以回应一些已经出现的讨论。

根据 dbush 给出的答案中引用的标准的 6.5p2,我们有两个条件明确声明分配具有未定义的行为:

1) 标量对象的副作用相对于同一标量对象的不同副作用是无序的。

2) 相对于使用相同标量对象的值的值计算,对标量对象的副作用是无序的。

第 1 项的示例是“i = ++i + 1”。在这种情况下,由于 ++i 将值 i+1 写入 i 的副作用相对于将 RHS 分配给 LHS 的副作用是无序的。在每一侧的值计算和将 RHS 分配给 LHS 之间存在一个序列点,如下面 Jens Gustedt 的回答中给出的 6.5.16.1 中所述。但是,由于 ++i 对 i 的修改不受该序列点的约束,否则将定义行为。

在我上面给出的例子中,我们有类似的情况。有一个值计算,它涉及创建一个数组并将该数组转换为指向其第一个元素的指针。将值写入该数组的一部分还有一个副作用, *p 写入第一个元素。

所以,我看不出我们在标准中有什么保证,即在将数组地址写入 p 之前,将对数组中未初始化的第一个元素的修改进行排序。这种修改(将 *p 写入第一个元素)与将 i+1 写入 i 的修改有何不同?

换句话说,假设一个实现将示例中感兴趣的语句视为三个任务:第一,为复合文字对象分配空间;第二:将指向所述空间的指针分配给p;第 3 步:将 *p 写入新分配空间中的第一个元素。RHS 和 LHS 的值计算将在赋值之前进行排序,因为计算 RHS 的值只需要地址。这种假设的实现在哪些方面不符合标准?

4

3 回答 3

6

需要看6.5.16.1中赋值运算符的定义

更新左操作数的存储值的副作用是在左操作数和右操作数的值计算之后排序的。操作数的评估是无序的。

所以在这里你清楚地看到它首先以任意顺序甚至同时计算两边的表达式,然后将右边的值存储到左边指定的对象中。

此外,您应该知道作业的 LHS 和 RHS 的评估方式不同。引用有点太长了,这里总结一下

  • 对于 LHS,评估留下“左值”,即 p未触及的对象。特别是它不查看对象的内容。

  • 对于 RHS,有“左值转换”,即对于在那里找到的任何对象(例如*p)加载该对象的内容

  • 如果 RHS 包含数组类型的左值,则此数组将转换为指向其第一个元素的指针。这就是您的复合文字所发生的事情。

编辑:您添加了另一个问题

这种修改(将 *p 写入第一个元素)与将 i+1 写入 i 的修改有何不同?

区别只是i在分配的 LHS 中,因此必须更新。复合文字中的数组不在 LHS 中,因此与更新无关。

于 2018-02-12T20:56:55.070 回答
4

C 标准的第 6.5p2 节详细说明了为什么这是有效的:

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

脚注 84 指出:

84) 本段呈现未定义的语句表达式,例如

i = ++i + 1;
a[i++] = i; 

同时允许

i = i + 1;
a[i] = i;

来自 6.5.2.5 的发布片段属于后者,因为没有副作用。

于 2018-02-12T20:16:13.097 回答
1

In (int [2]){*p},*p为复合文字提供初始值。这不是一项任务,也不是副作用。创建对象时,初始值是对象的一部分。不存在数组存在且未初始化的时刻。

p = (int [2]){*p}中,我们知道更新的副作用p是在计算右侧之后排序的,因为 C 2011 [N1570] 6.5.16 3 说“更新左侧操作数的存储值的副作用是在计算右侧的值之后排序的左右操作数。”</p>

于 2018-02-13T07:02:59.220 回答