考虑这段代码:
extern int A[2];
/* Just returns `p` back. */
extern int *identity(int *p);
int f(int *restrict p)
{
int *q = identity(p); /* `q` becomes "based on" `p` */
int *r = A + (p == q);
*p = 1;
*r = 2;
return *p;
}
这里的
r
是“基于”p
吗?按照标准来看,似乎应该是这样。特别是:
[...] 如果(在计算 E 之前执行 B 的某个序列点)将 P 修改为指向它所在的数组对象的副本,则称指针表达式 E 是基于对象 P 的先前指向的值会改变 E 的值。
哪里
P
是:
指向 T 类型的限制限定指针。
根据上述定义,
r
是基于p
的,因为“修改
p
会改变A + (p == q)
的值”(因此是r
)。 尽管r
肯定会指向A
数组内部,但它仍然必须基于p
,这与直觉相反。也许这就是我错的地方。
GCC 和 Clang 不认为上述内容是正确的。他们将
p
上的最后一个负载优化为 return 1;
。因此,f(&A[1])
会错误地返回 1
,而不是 2
。
实施者是否误解了标准?如果不是,那么
f(&A[1])
是未定义的行为,但是 为什么 那么,我错过了什么?
谢谢!
restrict
的正式定义是有问题的。这个措辞很好地传达了这个想法的精神,但如果你把它当作它声称的正式定义,那么它并没有真正达到(我认为的)预期目的。
这里
“基于”r
吗?p
是的,根据正式定义,前提是
identity(p)
返回 p
的值,正如其名称所示。您已经引用了相关文字。
在这种情况下,在评估 p
的初始化器之后和评估 q
的初始化器之前修改 r
将更改 r
的值。
但请记住,
restrict
是关于别名的,而“基于”是关于确定哪些表达式必须被视为彼此可能的别名,以便不必考虑restrict
限定指针的可能别名以及基于它们的表达式not。该规范不排除通过示例中的相等比较附加“基于”状态,但这应被视为规范中的缺陷。
GCC 和 Clang 不认为上述内容是正确的。他们将
上的最后一个负载优化为p
。因此,return 1;
会错误地返回f(&A[1])
,而不是1
。2
实施者是否误解了标准?
是和不是。他们正在实现我所认为的标准的意图,即
restrict
意味着他们可以假设p
不会为A
的任何成员起别名。
但是他们没有实现规范的字母:
令
为任何具有基于L
的&L
的左值。如果使用P
来访问 它指定的对象L
的值,并且X
也被修改(以任何方式),那么以下 要求适用:[...]用于访问值的每个其他左值X
也应具有基于X
的地址。每次修改P
的访问也应被视为 出于本子条款的目的,修改X
。P
(C17 6.7.3.1/4)
如果我们根据规范的实际措辞接受
r
基于p
,那么实现就必须表现得就像对*r
的赋值修改了p
一样,也就是说它不能假设 *p
在分配给 *r
后计算出与之前相同的值。
如果不是,那么
是未定义的行为,但为什么会这样,我错过了什么?f(&A[1])
我相信
f(&A[1])
是意图具有未定义的行为,因为在您的示例中,r
不应基于p
。如果确实不是,那么对 *r
的赋值将违反“用于访问 X
值的每个其他左值也应具有基于 P
的地址。”