C ++标准是否保证函数返回值具有常量地址?

时间:2016-06-26 21:19:44

标签: c++ c++11 return-value language-lawyer abi

考虑这个程序:

#include <stdio.h>
struct S {
  S() { print(); }
  void print() { printf("%p\n", (void *) this); }
};
S f() { return {}; }
int main() { f().print(); }

据我所知,这里只构建了一个S对象。没有副本删除:首先没有副本被删除,事实上,如果我明确删除了副本和/或移动构造函数,编译器继续接受该程序。

但是,我看到打印了两个不同的指针值。发生这种情况是因为我的平台的ABI在CPU寄存器中返回了一些可复制的类型,例如这个类型,所以ABI无法避免复制。 clang即使在完全优化掉函数调用时也会保留这种行为。如果我给S一个非平凡的复制构造函数,即使它不可访问,那么我确实看到相同的值打印了两次。

print()的初始调用发生在构造期间,即在对象生命周期开始之前,但在构造函数中使用this通常是有效的,只要它不以某种方式使用这需要构造完成 - 例如,没有铸造到派生类 - 据我所知,打印或存储它的值不需要构造完成。

标准是否允许此程序打印两个不同的指针值?

注意:我知道标准允许这个程序打印相同指针值的两个不同表示,从技术上讲,我没有排除它。我可以创建一个避免比较指针表示的不同程序,但它会更难理解,所以我想尽可能避免这种情况。

2 个答案:

答案 0 :(得分:5)

T.C。评论中指出,这是标准中的缺陷。这是core language issue 1590。这是一个与我的例子略有不同的问题,但原因相同:

  

有些ABI要求在寄存器[...]中传递某些类类型的对象。应更改标准以允许此用法。

current suggested wording将通过向标准添加新规则来涵盖这一点:

  

当类型X的对象传递给函数或从函数返回时,如果X的每个复制构造函数,移动构造函数和析构函数都是微不足道的或删除的,{{1}至少有一个未删除的副本或移动构造函数,允许实现创建一个临时对象来保存函数参数或结果对象。 [...]

在大多数情况下,这将允许当前的GCC / clang行为。

有一个小角落的情况:当前,当一个类型只有一个删除的副本或移动构造函数,如果默认是默认的,根据标准的当前规则,如果删除该构造函数仍然是微不足道的:

  

12.8复制和移动类对象[class.copy]

     

12如果不是用户提供的,X类的复制/移动构造函数是微不足道的[...]

删除的拷贝构造函数不是用户提供的,后面的任何内容都不会使这样的拷贝构造函数变得非常重要。因此,按照标准的规定,这样的构造函数是微不足道的,而as specified by my platform's ABI,由于琐碎的构造函数,GCC和clang也会在这种情况下创建一个额外的副本。我的测试程序的一行添加证明了这一点:

X

即使建议的分辨率要求打印两次相同的地址,也会打印出GCC和clang两个不同的地址。这似乎表明,虽然我们将更新标准以不需要完全不兼容的ABI,但我们仍然需要更新ABI以便以与标准所需的方式兼容的方式处理极端情况。 / p>

答案 1 :(得分:1)

在这种情况下,这不是一个答案,而是一个注释关于 g ++ clang 的不同行为,具体取决于{{ 1}}优化标志。

请考虑以下代码:

-O

我们可以看到clang(3.8)和g ++(6.1)有点不同,但两者都得到了正确的答案。

clang(没有-O,-O1,-O2)和g ++(没有-O,-O1)

#include <stdio.h>
struct S {
    int i;
    S(int _i): i(_i) { 
        int* p = print("from ctor");
        printf("about to put 5 in %p\n", (void *)&i);        
        *p = 5;
    }
    int* print(const char* s) { 
        printf("%s: %p %d %p\n", s, (void *) this, i, (void *)&i);
        return &i;
    }
};
S f() { return {3}; }
int main() { 
    f().print("from main");
}

g ++(for -O2)

from ctor: 0x7fff9d5e86b8 3 0x7fff9d5e86b8
about to put 5 in 0x7fff9d5e86b8
from main: 0x7fff9d5e86b0 5 0x7fff9d5e86b0

似乎他们在两种情况下都做对了 - 当他们决定跳过寄存器优化(g ++ -O2)时,以及当他们使用寄存器优化但是将值复制到实际i时(所有其他情况) )。