我一直认为,在赋值中读取正确表达式后缺少序列点会产生如下例子产生未定义的行为:
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.
然而,这是" 6.5.2.5复合文字"下的例2。在C11标准的委员会草案中,标识为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到第一个元素也会产生副作用。
所以,我不知道修改标准中我们有哪些gaurantees 对数组的其他未初始化的第一个元素进行排序 在将数组地址写入p之前。这种修改(将* p写入第一个元素)与写入修改不同 我+ 1到我?
换句话说,假设一个实现将示例中的兴趣语句看作三个任务:第一,为复合文字对象分配空间;第二步:将指向所述空间的指针分配给p; 3rd:将* p写入新分配空间中的第一个元素。在分配之前,RHS和LHS的值计算将被排序,因为计算RHS的值仅需要地址。这个假设的实现在哪种方式不符合标准?
答案 0 :(得分:6)
您需要查看6.5.16.1
中赋值运算符的定义更新左操作数的存储值的副作用是 在左右操作数的值计算之后排序。 对操作数的评估是不合理的。
所以在这里你清楚地看到,它首先以任意顺序或甚至同时评估双方的表达式,然后将右边的值存储到左边指定的对象中。
此外,您应该知道作业的LHS和RHS的评估方式不同。引文有点太长了,所以这里有一个摘要
对于LHS,评估会留下“左值”,即对象
p
,未受影响。特别是它没有看到的内容
对象
对于RHS,存在“左值转换”,即对于在那里找到的任何对象(例如*p
),该对象的内容被加载。
如果RHS包含数值类型的左值,则此数组将转换为指向其第一个元素的指针。这就是你的复合文字所发生的事情。
修改:您添加了另一个问题
这个修改怎么样(把* p写到第一个元素)是 不同于将i + 1写入i?
的修改
差异只是分配的LHS中的i
,因此必须更新。复合文字中的数组不在LHS中,因此无需更新。
答案 1 :(得分:4)
C standard的第6.5p2节详细说明了这有效的原因:
如果标量对象的副作用相对于其中任何一个都没有排序 对同一个标量对象或值有不同的副作用 使用相同标量对象的值进行计算,行为是 未定义。如果有多个允许的排序 表达式的子表达式,如果这样的话,行为是不确定的 在任何排序中都会出现无序的副作用。 84)
脚注84指出:
84)此段落呈现未定义的语句表达式,例如
i = ++i + 1; a[i++] = i;
允许
i = i + 1; a[i] = i;
6.5.2.5中的已发布片段属于后者,因为没有副作用。
答案 2 :(得分:1)
在(int [2]){*p}
中,*p
提供复合文字的初始值。这不是一项任务,也不是副作用。创建对象时,初始值是对象的一部分。数组不存在且没有初始化。
在p = (int [2]){*p}
中,我们知道更新p
的副作用在计算右侧后排序,因为C 2011 [N1570] 6.5.16 3说“更新存储的副作用在左右操作数的值计算之后,左操作数的值被排序。“