这个gcc / clang过去一指针比较行为是符合标准还是非标准?

sup*_*cat 9 c optimization gcc clang

即使C标准明确认识到,指向一个对象“刚刚过去”的地址可能偶然地与指向“指向”另一个不相关对象的地址相等,但gcc和clang似乎都在假设没有指针的情况下运行如示例所示,观察到指向刚刚超过一个对象的对象可能指向另一个对象:

#include <stdio.h>

int x[1],y[1];
int test1(int *p)
{
    y[0] = 1;
    if (p==x+1)
        *p = 2; // Note that assignment is to *p and not to x[1] !!!
    return y[0];
}
int test2(int *p)
{
    x[0] = 1;
    if (p==y+1)
        *p = 2; // Note that assignment is to *p and not to y[1] !!!
    return x[0];
}

int (*volatile test1a)(int *p) = test1;
int (*volatile test2a)(int *p) = test2;

int main(void) {
    int q;
    printf("%llX\n",(unsigned long long)y - (unsigned long long)x);
    q = test1a(y);
    printf(">> %d %d\n", y[0], q);
    q = test2a(x);
    printf(">> %d %d\n", x[0], q);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

通过阅读标准,此程序在标记的行上的有效输出>>将为>> 1 1>> 2 2,但>> 2 1其中一行的ideone输出上的gcc 以及从我可以知道clang生成的代码中的输出也将对另一行进行同样的处理。

我很清楚,p比较等于的事实x[1]并不意味着后者可以用于访问与*p(或任何对象)相同的对象,但是我不知道标准中的任何内容禁止计算x+1结果指针和之间的或比较p。我还知道,没有任何东西会导致这种比较p无法用于访问其拥有地址的对象。

是否有任何合理的阅读任何C标准的发布版本或草稿版本,在上述版本中上述代码将调用未定义行为,或者在该版本下,从test1test2不需要返回的值分别与y[0]或的最终值匹配x[0],或者是优化程序?用clang和gcc设计来处理不是标准的已发布或草稿版本的方言?

PS-根据标准草案N1570 6.5.9p6:

6当且仅当两个都是空指针时,两个指针比较相等,两个都是指向同一对象的指针(包括指向对象和它的开始处的子对象的指针)或函数,并且都是指向同一对象的最后一个元素的指针数组对象,或者一个是指向一个数组对象末尾的指针,另一个是指向另一个数组对象的起点的指针,而该数组对象恰好紧随地址空间中的第一个数组对象。

该标准不以任何方式暗示x[]必须遵循y[],反之亦然,但似乎明确规定了将指向过去的指针x与之进行比较y并视为相等的可能性。

Eri*_*hil 3

这确实是一个编译器错误。在大多数情况下,当某些指针、数组和整数的p == x+i计算结果为 true时,确实必然指向( 中的元素,或者程序正在执行具有未定义行为的代码,在这种情况下,允许编译器假设指向中的元素)。如果确实指向 中的元素,则该元素确实无法更改,因此编译器生成返回最近分配给 1 的代码是正确的。线索表明编译器已尝试进行优化,其中 \xe2\x80\x9clearns\xe2\x80\x9d 关于pxipxpxpx*p = 2;y[0]y[0]p通过比较的真实性。

\n\n

x+i当指向超出数组末尾的 1时,此推导失败x。在 C 2018 6.5.9 6 中,标准告诉我们,即使p指向与无关的不同对象x(但恰好在内存中紧随其后),此比较也可能评估为 true。编译器\xe2\x80\x99s的推导应该更加有限。给定p+j == x+i,其中x是一个元素数组n,计算为 true 的事实仅意味着指向for \xe2\x88\x92 \xe2\x89\xa4 < + \xe2\x88\x92p+k的元素。(在问题的情况下,=1、=0 和=0,失败 0\xe2\x88\x921 \xe2\x89\xa4 0 < 1+0\xe2\x88\x921。)xjiknjiijk

\n\n

(请注意,上面提出的标准意味着,对于问题中的情况,p-1指向 的一个元素x,因为 0\xe2\x88\x921 \xe2\x89\xa4 \xe2\x88\x921 < 0 成立,但我们知道p指向y且对于指向 无效x。但是使用它来访问p[-1]具有标准未定义的行为,这意味着任何假设都是允许的,包括这p[-1]x。因此允许编译器使用该标准。)

\n

  • 我不同意。我认为编译器可能会假设 x 和 y 的顺序没有保证。它们最终可能会在它们之间进行填充,由编译器生成 xy 或 yx 或由链接器脚本以不同方式排序。我相信编译器假设它们在内存中没有相互跟随是有效的,因为没有给出这样的保证,并假设最适合它的顺序来优化代码。 (3认同)
  • @GoswinvonBrederlow:很明显,在这种情况下,编译器所做的假设破坏了符合要求的程序。问题中程序中的所有代码均已定义 - 程序没有访问数组边界之外,也没有使用一个对象的指针来访问不相关的对象。它只是执行了一个比较,并且作为该比较的结果,编译器假设某些东西是错误的,并且以可观察的方式是错误的,并且违反了 C 标准所要求的行为。 (2认同)