两个临时表的地址是否保证在同一个表达式中不同?

cig*_*ien 30 c++ pointers language-lawyer temporary-objects

考虑以下程序:

#include <iostream>

int const * f(int const &i) 
{ 
  return &i; 
}

int main() 
{
  std::cout << f(42);  // #1
  std::cout << f(42);  // #2

  std::cout << f(42) << f(42);  // #3
}
Run Code Online (Sandbox Code Playgroud)

根据编译器和设置的优化级别,打印在行上的地址可能彼此不同#1#2也可能不同。

但是,无论选择何种编译器或优化级别,行#3中打印的 2 个地址总是彼此不同。

这是一个可以玩的演示

那么f在每种情况下返回什么的规则是什么?

Qui*_*mby 34

C++ 中的两个活动对象(几乎)总是有不同的地址。

由于 #1 #2 中的临时变量具有不重叠的生命周期,编译器可以自由地将 #1 的存储重用于 #2 。

但是在#3 中,所有临时变量在表达式结束前都还活着(出于显而易见的原因),在这种情况下,它们必须具有不同的地址。

除了“as if”规则之外,C++ 不支持对相同子表达式的保证缓存。这意味着如果你不获取地址,编译器存储它们是完全合理的,但是它喜欢或根本不存储它们。

参考

N4861 Draft C++20 [6.7.9.2] 除非对象是位域或零大小的子对象,否则该对象的地址是它占用的第一个字节的地址。如果一个对象嵌套在另一个对象中,或者如果至少一个对象是零大小的子对象并且它们的类型不同,则两个生命周期重叠且不是位字段的对象可能具有相同的地址;否则,它们具有不同的地址并占用不相交的存储字节。^28

在您的情况下,例外情况不适用。脚注 ^28 也准确地说明了我上面写的内容:

^28:在“as-if”规则下,如果程序无法观察到差异,则允许实现将两个对象存储在同一机器地址或根本不存储对象。

编辑

来自@RiaD 的好问题:

但这两个 42 必须是不同的对象吗?例如,“abc”和“abc”可以是同一个数组。

该行为取决于所使用的文字类型,并在N4861 Draft C++20 5.13 [lex.literal] 中进行了精确定义。

  1. 字符串文字是所有文字类型中的一个例外,因为它们被归类为左值,因此具有地址。

    [lex.string.14]对字符串字面量值会产生一个具有静态存储持续时间的字符串字面量对象,从上面指定的给定字符初始化。是否所有字符串文字都是不同的(即存储在非重叠对象中)以及字符串文字的连续计算是否产生相同或不同的对象是未指定的。

    这意味着文字可能与@RiaD 观察到的地址相同,但这与上述并不矛盾,因为它们是同一个对象。

  2. 所有其他文字,包括整数,都是 prvalue 表达式,它们不是对象(从某种意义上说,它们没有地址),但在某些情况下,它们通过临时物化产生临时对象,foo(42)因为它绑定到const T&. AFAIK 标准没有明确说相同的两个纯右值表达式必须产生不同的临时值,但它说一个表达式初始化一个临时值,所以我相信每个表达式都必须创建一个新的临时值,生命周期也略有不同。因此,两个地址(如果观察到)必须不同。

  • 但也有例外(不适用于所讨论的情况)。即空基类和具有属性 [[no_unique_address]] 的成员可以与其他子对象共享地址。当然,第一个子对象共享其超级对象的地址。 (4认同)
  • 但这两个 42 必须是不同的对象吗?例如,“abc”和“abc”可以是同一个数组。https://gcc.godbolt.org/z/f4zPTP (2认同)
  • @RiaD 编辑了答案,很好的问题! (2认同)

Sto*_*ica 18

临时对象会一直持续到使它们恢复生机的完整表达结束。

[类.临时]

4 ... 临时对象被销毁作为评估完整表达式([intro.execution])(词汇上)包含它们创建点的最后一步。

所有临时对象都是如此。这意味着在表达式#3 中,假设它的计算结束没有抛出异常,两个临时变量可能有重叠的生命周期。

除了少数例外(此处均不适用),两个不同的对象在其生命周期内将具有不同的地址。