在指针赋值中使用指向内容
Using pointed to content in assignment of a pointer
我一直认为在读取赋值中的正确表达式后缺少序列点会导致如下示例产生未定义的行为:
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到第一个元素)和writing的修改有什么区别
i+1 到 i?
换句话说,假设一个实现将示例中感兴趣的语句视为三个任务:1,为复合文字对象分配space;第二:将指向space的指针分配给p;第三次:将*p写入新分配的space中的第一个元素。 RHS 和 LHS 的值计算将在分配之前排序,因为计算 RHS 的值只需要地址。这个假设的实现在哪些方面不符合标准?
C standard 的第 6.5p2 节详细说明了为什么这是有效的:
If a side effect on a scalar object is unsequenced relative to either
a different side effect on the same scalar object or a value
computation using the value of the same scalar object, the behavior is
undefined. If there are multiple allowable orderings of the
subexpressions of an expression, the behavior is undefined if such an
unsequenced side effect occurs in any of the orderings. 84)
脚注 84 指出:
84) This paragraph renders undefined statement expressions such as
i = ++i + 1;
a[i++] = i;
while allowing
i = i + 1;
a[i] = i;
6.5.2.5 中发布的代码片段属于后者,因为没有副作用。
需要看6.5.16.1中赋值运算符的定义
The side effect of updating the stored value of the left operand is
sequenced after the value computations of the left and right operands.
The evaluations of the operands are unsequenced.
所以这里你清楚的看到,首先它以任意顺序甚至同时对两边的表达式进行求值,然后然后将权值存入到指定的对象中离开了。
此外,您应该知道作业的左轴和右轴的评估方式不同。引用有点太长,所以这里是摘要
对于LHS评价留下"lvalues",即对象如
p
,未受影响。特别是它不看的内容
对象.
对于 RHS 有 "lvalue conversion",对于在那里找到的任何对象(例如 *p
)contents该对象的已加载。
如果 RHS 包含数组类型的左值,则此数组将转换为指向其第一个元素的指针。这就是您的复合文字正在发生的事情。
编辑:您添加了另一个问题
What about this modification (writing *p to the first element) is
different from the modification of writing i+1 to i?
区别仅在于分配的 LHS 中的 i
,因此必须更新。来自复合文字的数组不在 LHS 中,因此与更新无关。
在(int [2]){*p}
中,*p
为复合文字提供了一个初始值。这不是一项任务,也不是副作用。创建对象时,初始值是对象的一部分。没有数组存在的时刻没有初始化。
在p = (int [2]){*p}
中,我们知道更新p
的副作用是在右侧计算之后排序的,因为C 2011 [N1570] 6.5.16 3说“更新的副作用左操作数的存储值在左右操作数的值计算之后排序。”
我一直认为在读取赋值中的正确表达式后缺少序列点会导致如下示例产生未定义的行为:
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到第一个元素)和writing的修改有什么区别 i+1 到 i?
换句话说,假设一个实现将示例中感兴趣的语句视为三个任务:1,为复合文字对象分配space;第二:将指向space的指针分配给p;第三次:将*p写入新分配的space中的第一个元素。 RHS 和 LHS 的值计算将在分配之前排序,因为计算 RHS 的值只需要地址。这个假设的实现在哪些方面不符合标准?
C standard 的第 6.5p2 节详细说明了为什么这是有效的:
If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings. 84)
脚注 84 指出:
84) This paragraph renders undefined statement expressions such as
i = ++i + 1; a[i++] = i;
while allowing
i = i + 1; a[i] = i;
6.5.2.5 中发布的代码片段属于后者,因为没有副作用。
需要看6.5.16.1中赋值运算符的定义
The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. The evaluations of the operands are unsequenced.
所以这里你清楚的看到,首先它以任意顺序甚至同时对两边的表达式进行求值,然后然后将权值存入到指定的对象中离开了。
此外,您应该知道作业的左轴和右轴的评估方式不同。引用有点太长,所以这里是摘要
对于LHS评价留下"lvalues",即对象如
p
,未受影响。特别是它不看的内容 对象.对于 RHS 有 "lvalue conversion",对于在那里找到的任何对象(例如
*p
)contents该对象的已加载。如果 RHS 包含数组类型的左值,则此数组将转换为指向其第一个元素的指针。这就是您的复合文字正在发生的事情。
编辑:您添加了另一个问题
What about this modification (writing *p to the first element) is different from the modification of writing i+1 to i?
区别仅在于分配的 LHS 中的 i
,因此必须更新。来自复合文字的数组不在 LHS 中,因此与更新无关。
在(int [2]){*p}
中,*p
为复合文字提供了一个初始值。这不是一项任务,也不是副作用。创建对象时,初始值是对象的一部分。没有数组存在的时刻没有初始化。
在p = (int [2]){*p}
中,我们知道更新p
的副作用是在右侧计算之后排序的,因为C 2011 [N1570] 6.5.16 3说“更新的副作用左操作数的存储值在左右操作数的值计算之后排序。”