C ++标准是否保证函数返回值具有常量地址? [英] Does the C++ standard guarantee that a function return value has a constant address?

查看:179
本文介绍了C ++标准是否保证函数返回值具有常量地址?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑这个计划:

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

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



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



初始调用 print()在构造过程中发生,这是在对象生命周期开始之前,但使用 this 在构造函数内部通常是有效的,只要它不需要一个方式,要求构造已完成 - 没有转换到派生类,例如 - 据我所知,打印或



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



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

解决方案

TC在评论中指出这是标准中的缺陷。这是核心语言问题1590 。这是一个与我的例子不同的问题,但同样的根本原因:


一些ABI需要传递某些类类型的对象[...]应更改此标准以允许此用法。


当前建议的措辞将通过向标准中添加一个新规则来实现:


当类型 X 的对象传递给函数或从函数返回时,如果每个复制构造函数,移动构造函数和析构函数 X 是平凡的或删除的,并且 X 至少有一个未删除的复制或移动构造函数,允许实现创建一个临时对象保存函数参数或结果对象。 [...]


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



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


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



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


删除的拷贝构造函数不是用户提供的,下面的任何内容都不会使这样的拷贝构造函数变得不重要。因此,根据标准的规定,此类构造函数很简单,而指定的我的平台的ABI ,因为琐碎的构造函数,GCC和clang在这种情况下创建一个额外的副本。对我的测试程序的一行补充说明了这一点:

  #include< stdio.h> 
struct S {
S(){print(); }
S(const S&)= delete;
void print(){printf(%p \\\
,(void *)this); }
};
S f(){return {}; }
int main(){f()。print(); }

这将打印两个不同的地址与GCC和铛,即使提议的分辨率将需要相同的地址被打印两次。这似乎表明,虽然我们将获得标准的更新,以不需要一个完全不兼容的ABI,我们仍然需要更新ABI,以便以符合标准要求的方式处理角点。 / p>

Consider this program:

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

As far as I can tell, there is exactly one S object constructed here. There is no copy elision taking place: there is no copy to be elided in the first place, and indeed, if I explicitly delete the copy and/or move constructor, compilers continue to accept the program.

However, I see two different pointer values printed. This happens because my platform's ABI returns trivially copyable types such as this one in CPU registers, so there is no way with that ABI of avoiding a copy. clang preserves this behaviour even when optimising away the function call altogether. If I give S a non-trivial copy constructor, even if it's inaccessible, then I do see the same value printed twice.

The initial call to print() happens during construction, which is before the start of the object's lifetime, but using this inside a constructor is normally valid so long as it isn't used in a way that requires the construction to have finished -- no casting to a derived class, for instance -- and as far as I know, printing or storing its value doesn't require the construction to have finished.

Does the standard allow this program to print two different pointer values?

Note: I'm aware that the standard allows this program to print two different representations of the same pointer value, and technically, I haven't ruled that out. I could create a different program that avoids comparing pointer representations, but it would be more difficult to understand, so I would like to avoid that if possible.

解决方案

T.C. pointed out in the comments that this is a defect in the standard. It's core language issue 1590. It's a subtly different issue than my example, but the same root cause:

Some ABIs require that an object of certain class types be passed in a register [...]. The Standard should be changed to permit this usage.

The current suggested wording would cover this by adding a new rule to the standard:

When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. [...]

For the most part, this would permit the current GCC/clang behaviour.

There is a small corner case: currently, when a type has only a deleted copy or move constructor that would be trivial if defaulted, by the current rules of the standard, that constructor is still trivial if deleted:

12.8 Copying and moving class objects [class.copy]

12 A copy/move constructor for class X is trivial if it is not user-provided [...]

A deleted copy constructor is not user-provided, and nothing of what follows would render such a copy constructor non-trivial. So as specified by the standard, such a constructor is trivial, and as specified by my platform's ABI, because of the trivial constructor, GCC and clang create an extra copy in that case too. A one-line addition to my test program demonstrates this:

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

This prints two different addresses with both GCC and clang, even though even the proposed resolution would require the same address to be printed twice. This appears to suggest that while we will get an update to the standard to not require a radically incompatible ABI, we will still need to get an update to the ABI to handle the corner case in a manner compatible with what the standard will require.

这篇关于C ++标准是否保证函数返回值具有常量地址?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆