即使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;
}
通过阅读《标准》,该程序在标记为>>
的行上的有效输出将为>> 1 1
或>> 2 2
,但是ideone输出>> 2 1
上的gcc为其中一行,从我可以看出clang生成的代码对另一行也是如此。
我很清楚p
等于x[1]
的事实并不意味着后一个表达式可用于访问与*p
相同的对象(或位于全部,就此而言),但我不知道标准中的任何内容都禁止计算x+1
以及结果指针与p
之间的比较。我还知道没有任何东西会使这种比较使p
无法用于访问其地址所在的对象。
是否存在对上述标准调用未定义行为或不需要从test1
和test2
返回的值的C标准的任何已发布或草稿版本的合理阅读?分别与y[0]
或x[0]
的最终值匹配,还是clang和gcc中的优化器设计用于处理不是标准的已发布或草稿版本的方言?
PS-根据标准草案N1570 6.5.9p6:
6当且仅当两个都是空指针,并且两个都是指向指针的指针时,两个指针比较相等 相同的对象(包括指向对象和子对象开头的指针)或函数, 两者都是指向同一数组对象最后一个元素之后的指针,或者一个为指针 指向一个数组对象的末尾,另一个是指向另一个数组的开始的指针 恰好紧跟地址中第一个数组对象的数组对象 空间。
该标准并不以任何方式暗示x[]
必须遵循y[]
,反之亦然,但这似乎是在明确规定指针指向x
的指针可能会与y
进行比较并观察为相等。
答案 0 :(得分:1)
不,这不是编译器错误。您的程序有未定义的行为。
在函数 test1
中,您将 p 与 x+1 进行比较,但 p==y 因此指针完全不相关,这会给您带来不确定的行为。编译器不需要为此发出警告。在这种情况下是不可能检测到的。
因此,两个函数中与 p 的比较结果可能为真,也可能不为真,但无法预测会发生什么,具体取决于内存布局。
编译器错误很少见,如果您认为自己发现了错误,请三思并寻找未定义的行为。
答案 1 :(得分:0)
这确实是一个编译器错误。在大多数情况下,当p == x+i
的某个指针p
,数组x
和整数i
的值为true时,p
必然指向一个x
中的元素(或程序正在执行具有未定义行为的代码,在这种情况下,允许编译器假定p
指向x
中的元素)。如果p
确实指向x
中的元素,那么*p = 2;
不能更改y[0]
是正确的,因此编译器生成返回以下内容的代码是正确的最近分配给y[0]
的1。这些线索表明,编译器已尝试进行优化,以通过比较的事实“了解” p
。
当x+i
指向数组x
的末尾时,此推论失败。在C 2018 6.5.9 6中,该标准告诉我们,即使p
指向与x
无关的另一个对象,该比较也可能为true(但是恰好在内存中紧随其后) 。编译器的推论应该更加有限。给定p+j == x+i
,其中x
是n
个元素的数组,则评估为真的事实仅意味着p+k
指向{ {1}}-x
≤j
<i
+ k
-n
。 (在问题中,j
= 1,i
= 0和i
= 0,这使0-1≤0 <1 + 0-1失败。)>
(请注意,对于问题的情况,上述准则暗示j
指向k
的元素,因为0−1≤-1 <0成立,但我们知道p-1
指向x
,对指向p
无效,但是使用它来访问y
的行为未由标准定义,这意味着可以进行任何假设,包括x
是p[-1]
的元素。因此允许编译器使用该准则。)