请考虑以下代码:

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的值,则指针表达式E被称为基于对象P.

其中P是:

指向类型T的限制限定指针.

根据上述定义,r is基于p,因为"修改p将改变A + (p == q)的值"(因此r‘S).Even though r肯定会指向A数组内部,它仍然必须以p为基础,这是违反直觉的.也许这就是我错的地方.

GCC和Clang do not认为上述情况属实.他们将p上的最后一次加载优化为return 1;.因此,f(&A[1])会错误地返回1,而不是2.

执行者是否误解了标准?如果不是,那么f(&A[1])是未定义的行为,但是why,我错过了什么?

谢谢!

推荐答案

restrict的正式定义是有问题的.措辞相当好地传达了这个 idea 的精神,但如果你把它作为它声称的正式定义,那么它就不能真正服务于(我认为是)预期的目的.

r是以p为基础的吗?

是的,根据正式定义,如果identity(p)返回p的值,正如它的名字所示.您已经引用了相关文本. 在这种情况下,在判断q的初始化器之后和判断r的初始化器之前修改p将改变r的值.

但是请记住,restrict是关于别名的,而"基于"是关于确定哪些表达式必须被认为是彼此的可能别名,以便不必考虑具有基于它们的表达式notrestrict限定指针的可能别名.该规范并不排除通过您的示例中的平等比较来附加"基于"状态,但这应该被认为是规范中的一个缺陷.

GCC和Cang Do not认为上述情况属实.他们将最后一次加载p次优化到仅return 1;次.因此,f(&A[1])将错误地返回1,而不是2.

实施者是否曲解了标准?

是也不是.他们正在实现我认为的标准的第intent条,即restrict意味着他们可以假设p不会成为A的任何成员的别名.

但他们没有实施规范的letter:

L是以P为基础的&L的任意左值.如果使用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])intended具有未定义的行为,因为在您的示例中r不应该基于p.如果确实不是,则对*r的赋值将违反"用于访问X的值的每一个其他左值也应具有基于P的地址".

C++相关问答推荐

为什么这个select()会阻止?

有效地计算由一组点构成的等边三角形和等腰三角形的数量

C中的__attributor__((aligned(4),packed))与 struct 的用法

为什么双重打印与C中的float具有不同的大小时具有相同的值?

致命:ThreadSaniizer:在Linux内核6.6+上运行时意外的内存映射

CSAPP微型shell 实验室:卡在sigprocmask

预先分配虚拟地址空间的区域

如何按顺序将所有CSV文件数据读入 struct 数组?

强制转换变量以在 struct 中蚕食

如何在GDB中查看MUSL的源代码

不确定如何处理此编译错误

将 struct 数组写入二进制文件时发生Valgrind错误

在运行时判断C/C++指针是否指向只读内存(在Linux操作系统中)

unions 的原子成员是个好主意吗?

Malloc和对齐

未为同一文件中的函数执行DirectFunctionCall

将char*铸造为空**

clion.我无法理解 Clion 中发生的 scanf 错误

多行表达式:C 编译器如何处理换行符?

inline 关键字导致 Clion 中的链接器错误