为什么两个相同的指针与 -O1 比较不相等?

ean*_*mos 3 c gcc pointers clang language-lawyer

#include <stdio.h>

int main(void)
{
    int a, b;
    int *p = &a;

#ifdef __clang__
    int *q = &b + 1;
#elif __GNUC__
    int *q = &b - 1;
#endif

    printf("%p %p %d\n", (void *)p, (void *)q, p == q);
}
Run Code Online (Sandbox Code Playgroud)

C11 § 6.5.9 \ 6 说

两个指针比较相等当且仅当它们都是空指针,都是指向同一个对象的指针(包括指向一个对象的指针和它开头的子对象)或函数,都是指向同一数组最后一个元素之后的指针对象,或者一个是指向一个数组对象末尾的指针,另一个是指向另一个数组对象开头的指针,该对象恰好紧跟地址空间中的第一个数组对象。

我已经通过四种不同的方式对其进行了测试:

  1. Clang 9.0.1 带-01选项;
  2. Clang 9.0.1 没有任何选项;
  3. GCC 9.2.0 带-01选项;
  4. GCC 9.2.9 没有任何选项。

结果如下:

$ ./prog_clang
0x7ffebf0a65d4 0x7ffebf0a65d4 1
$ ./prog_clang_01
0x7ffd9931b9bc 0x7ffd9931b9bc 1
$ ./prog_gcc
0x7ffea055a980 0x7ffea055a980 1
$ ./prog_gcc_01
0x7fffd5fa5490 0x7fffd5fa5490 0
Run Code Online (Sandbox Code Playgroud)

在这种情况下,正确的行为是什么?

And*_*nle 6

在这种情况下,正确的行为是什么?

空无一人。比较指向或超过两个完全不相关对象的末尾的指针是未定义的行为。

根据C11 标准的脚注 109(粗体是我的):

两个对象在内存中可能是相邻的,因为它们是较大数组的相邻元素或结构的相邻成员,它们之间没有填充,或者因为实现选择这样放置它们,即使它们不相关。如果先前的无效指针操作(例如数组边界外的访问)产生未定义行为,则后续比较也会产生未定义行为。

  • “_比较两个完全不相关的对象的指针或末尾的指针是未定义的行为_”这显然是 200% 错误的。文字上错误,精神上错误 C. (2认同)
  • “比较两个完全不相关的对象的指针或超出其末尾的指针是未定义的行为”这句话显然是错误的。根据 C 2018 6.5.8 5,关系运算符未定义此类指针的比较。但定义了与相等运算符的比较。6.5.9 给出了一个“当且仅当”,它定义了两个指针的**每种**情况的结果,并且 6.5.9 中没有任何内容对它们是否指向同一对象提出约束。 (2认同)

Mik*_*kis 5

两个指针比较相等当且仅当两者都是空指针时,

他们不是空的

两者都是指向同一对象的指针(包括指向对象的指针和位于其开头的子对象)或函数

它们不指向同一个对象,也不是子对象,也不是函数

两者都是指向同一数组对象最后一个元素的指针,

它们不是指向数组元素的指针。

或者一个是指向一个数组对象末尾的指针,另一个是指向另一个数组对象开始的指针,该对象恰好紧跟在地址空间中的第一个数组对象之后。

它们不是指向数组元素的指针。


因此,根据标准,您的指针不符合相等比较的要求,并且不应该进行相等比较。

现在,在您的测试中,在前三种情况下,指针实际上比较相等。可以说编译器没有严格遵守标准,因为标准说“当且仅当”,但正如您所见,不带 -O1 的 clang 和 gcc 表现得好像标准说“如果”而没有“和”只有当”部分。编译器只是不尝试采取额外措施来确保“且仅当”部分得到尊重,因此他们允许指针比较相等,这纯粹是巧合,尽管事实上根据标准,它们不应该。

由于这纯粹是巧合,在最后一种情况下,由于与编译器的优化实现有关的许多未知原因,这种巧合不再成立。编译器可能已经决定颠倒堆栈中变量的顺序,或者让它们彼此远离,或者谁知道是什么。

  • @walnut:“a”和“b”确实可以被视为单元素数组。仍然不允许您比较“&amp;a”和“&amp;b”的指针算术结果 - 它们是不同的单元素数组。 (2认同)
  • @Mat但这就是这个答案中最后一个引用所指定的内容。如果它们指向不同的数组(或被视为数组的对象),其中一个位于末尾,一个位于开头,则定义结果,并且根据内存布局的情况,定义为“true”或“false”被选择。 (2认同)
  • 这个答案是错误的;出于“==”运算符的目的,指针确实指向数组元素。C 2018 6.5.9 7 说“就这些运算符而言,指向不是数组元素的对象的指针的行为与指向长度为 1 且对象类型相同的数组的第一个元素的指针的行为相同作为它的元素类型。” (2认同)