C 编译器遵循“`restrict` 的正式定义”吗?

问题描述 投票:0回答:1

考虑这段代码:

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])
是未定义的行为,但是 为什么 那么,我错过了什么?

谢谢!

c pointers language-lawyer c99 restrict-qualifier
1个回答
0
投票

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
的地址。”

© www.soinside.com 2019 - 2024. All rights reserved.