【问题标题】:Can two different objects with automatic storage duration compare equal under address comparison?自动存储时长的两个不同对象在地址比较下可以比较相等吗?
【发布时间】:2018-06-03 07:50:23
【问题描述】:

具体来说,是否允许两个自动变量在不同函数中的地址比较相等,如下:

sink.c

#include <stdio.h>
#include <stdlib.h>

void sink(void *l, void *r) {
    puts(l == r ? "equal" : "not equal");
    exit(0);
}

ma​​in.c

typedef struct { char x[32]; } Foo;

void sink(void *l, void *r);

Foo make(void *p) {
    Foo f2;
    sink(&f2, p);
    return f2;
}

int main() {
    Foo f1 = make(&f1);
}

我希望这会打印not equal,因为f1f2 是不同的对象。使用 gcc 我得到 not equal,但使用本地版本的 clang 3.81,当编译为 clang -O1 sink.c main.c2 时,它会打印 equal

拆解makemain ...

0000000000400570 <make>:
  400570:   53                      push   rbx
  400571:   48 89 fb                mov    rbx,rdi
  400574:   e8 d7 ff ff ff          call   400550 <sink>
  400579:   48 89 d8                mov    rax,rbx
  40057c:   5b                      pop    rbx
  40057d:   c3                      ret    
  40057e:   66 90                   xchg   ax,ax

0000000000400580 <main>:
  400580:   48 83 ec 28             sub    rsp,0x28
  400584:   48 8d 7c 24 08          lea    rdi,[rsp+0x8]
  400589:   48 89 fe                mov    rsi,rdi
  40058c:   e8 df ff ff ff          call   400570 <make>
  400591:   31 c0                   xor    eax,eax
  400593:   48 83 c4 28             add    rsp,0x28
  400597:   c3                      ret    

...我们看到make 似乎根本没有创建Foo f2 对象,它只是使用现有的rdirsilr 参数调用sink , 分别)。这些由main 传递并且是相同的:第一个rdi 是指向放置返回值的位置的隐藏指针,第二个是&amp;f1,所以我们希望它们是相同的。


1我检查了最高 7.0 的版本,行为大致相同。

2 它发生在-O1-O2-O3,但不是-O0,而是打印not equal

【问题讨论】:

  • @r3musn0x on the [c] 标签问题是关于标准 C 的,除非另有说明
  • 我在问语言是否允许这样做,就像大多数“X 可以发生”的问题一样。在我的情况下,指针是否比较相等尚不清楚:也许我的程序中潜伏着一些 UB,它可以让编译器做它想做的任何事情。我想我本可以完全排除 clang 行为,但编译器行为通常可以作为标准允许的一个很好的积极测试。 @r3
  • 编译器错误。过分热心的优化器。结果必须是“不等于”。
  • @RbMm - 那是 C++,但这个问题是关于 C 的。在 C++ 中,规则是不同的,尤其是 RVO。使用 Godbolt 中的下拉菜单选择 C。

标签: c x86 clang


【解决方案1】:

C11 标准部分 6.5.9/6 说:

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

在这段代码中,列出的条件都不成立; &amp;f1&amp;f2 是指向不同对象的指针,其中一个不是另一个的子对象。

所以指针不能比较相等。报告equal 的编译器不符合要求。


注意:如果有人对Foo f1 = make(&amp;f1);的合法性有疑问,see this question。没关系,自动对象的生命周期从前面的{开始。

【讨论】:

  • 你应该添加6.2.4p6的第一句话
  • 啊,所以你也可以在这里链接那个,对于一个普通的读者。我还没有读过另一本。
  • 可能是这个和编译器 (gcc,clang,zapcc) 的错误,但是构造,当函数返回对象(不适合一般寄存器大小)设计不良(我的观点)。从技术上直接做到这一点是不可能的。当某些函数 (T fn()) 返回对象时 - 编译器需要将额外的隐藏参数传递给该函数 - 指向对象的指针 - T t = fn(); 将始终实现为 T t; fn(&amp;t)。如果我们编写构造 T fn(T*)T t = fn(&amp;t) - 一些编译器将其实现为 T t; fn(&amp;t, &amp;t) 并不奇怪
  • @RbMm 我们也不感到惊讶,但它仍然是错误的。
  • @RbMm - 如问题的评论线程中所述,其他编译器在 C++ 中执行此操作,而不是 C。在 C++ 中,由于 RVO 和/ 或 NRVO,它明确允许在这种情况下省略对象。
猜你喜欢
  • 2012-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-04
相关资源
最近更新 更多