29

Consider the following program:

#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
}

Depending on the compiler, and optimization level that is set, the addresses printed on lines #1 and #2 may or may not be different from one another.

However, regardless of choice of compiler, or optimization levels, the 2 addresses printed on line #3 are always different from one another.

Here's a demo to play around with.

So what are the rules for what f returns in each of these cases?

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
cigien
  • 57,834
  • 11
  • 73
  • 112
  • Does this answer your question? [C++ lifetime of temporaries - is this safe?](https://stackoverflow.com/questions/26793315/c-lifetime-of-temporaries-is-this-safe) – Language Lawyer Dec 17 '20 at 13:23
  • @LanguageLawyer Not really. Though the relevant passages seem to have overlap for both questions, I think the questions themselves are different. – cigien Dec 17 '20 at 15:25
  • 1
    This of course also applies if we make 42 a pre-processed-in macro value. I personally cannot see why any standard would *require* duplication of identical immutables. – mckenzm Dec 17 '20 at 19:24
  • Seems that everyone missed that `<<` here has implementation-defined behavior to pointers, so if you define "different" upon the program behavior, it is simply unspecified for arbitrary conforming implementations. – FrankHB Dec 19 '20 at 20:16
  • Perhaps you can rephrase the program using `==` instead. Note that there can be [other issues](https://stackoverflow.com/a/30694084/2307646) in some slightly different cases... – FrankHB Dec 19 '20 at 20:27
  • @FrankHB I had indeed considered using `==` and other ways of getting the address in the same expression, but they had issues as you point out. The `<<` for pointers being implementation defined is interesting. I don't think it affects the question particularly, since the answer should apply for any implementation choice. – cigien Dec 20 '20 at 07:40
  • There is also a another similar question here : [Force C++ to assign new addresses to arguments](https://stackoverflow.com/questions/65332620/force-c-to-assign-new-addresses-to-arguments) – Orçun Çolak Dec 22 '20 at 05:50
  • @OrçunÇolak Yes, in fact that's where this question came up, as can be seen in the comments on the accepted answer. – cigien Dec 23 '20 at 03:27
  • Two different objects are just... not the same one, as they have different *identities*. The notion of identity is certainly more broadly used, e.g. for lvalues, even though it is also carefully dodged in most contexts. If the difference of identity is interested, the specification just mandates the only allowed manners of the *accesses* to the objects (e.g. strict aliasing rules), because how many objects here is considered an implementation detail. An address is conceptionally *derived* from the identity of the objects and it cannot help you to make the difference more obvious. – FrankHB Dec 31 '20 at 12:22
  • It is proper to rely on the notion of address to describe the layout among different objects (and subobjects thereof). In this particular context, identity is not enough. This is not the case here (overlapping lifetime, not overlapping storage). The reasoning becomes chaos when addresses are involved. As answered, since the as-if rules are effective, different objects can have the same address when there is no portable way to differentiate the addresses. Also note that `addressof` and `[[no_unique_address]]` do not really require to differentiate the addresses (but just identities). – FrankHB Dec 31 '20 at 12:37

3 Answers3

33

Two alive objects in C++ (almost) always have different addresses.

Since temporaries in #1 #2 have non-overlapping lifetimes, the compiler is free to reuse the storage of #1 for #2 .

But in #3 all temporaries are alive until the end of the expression (for obvious reasons) and in this case they have to have different addresses.

C++ does not support guaranteed caching of the same sub-expressions apart from the "as if" rule. Meaning that if you do not take the address, it is perfectly reasonable for the compiler to store them however it likes or not store them at all.

Reference

N4861 Draft C++20 [6.7.9.2] Unless an object is a bit-field or a subobject of zero size, the address of that object is the address of the first byte it occupies. Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are of different types;otherwise, they have distinct addresses and occupy disjoint bytes of storage. ^28

In your case, the exceptions do not apply. The footnote ^28 also says exactly what I have written above:

^28: Under the “as-if” rule an implementation is allowed to store two objects at the same machine address or not store an object at all if the program cannot observe the difference.

Edit

Excellent question from @RiaD:

But do these two 42s have to be different objects? For example, "abc" and "abc" can be the same array.

The behaviour depends on the kind of the literal used and is precisely defined in N4861 Draft C++20 5.13 [lex.literal].

  1. String literals are an exception among all literal kinds because they are classified as lvalues and thus have an address.

    [lex.string.14] Evaluating a string-literal results in a string literal object with static storage duration, initialized from the given characters as specified above. Whether all string-literals are distinct (that is, are stored in nonoverlapping objects) and whether successive evaluations of a string-literal yield the same or a different object is unspecified.

    Meaning the literals might have the same address as @RiaD observed but that is not in contradiction with the above because they are the same object.

  2. All other literals, including integers, are prvalue expressions which are not objects (in a sense that they do not have an address), but in certain cases they spawn a temporary object through temporary materialization which happens for foo(42) because it is bound to a const T&. AFAIK the Standard does not explicitly say that same two prvalue expressions have to spawn a different temporary, but it says that an expression initializes a temporary, so I believe each expression has to create a new temporary, the lifetimes are also slightly different. So, two addresses (if observed) must be different.

Quimby
  • 17,735
  • 4
  • 35
  • 55
  • 4
    There are exceptions though (which do not apply to the case in question). Namely empty base classes and members with attribute [[no_unique_address]] may share address with other sub objects. And of course the first sub object shares the address of its super. – eerorika Dec 17 '20 at 10:26
  • 2
    but do these two 42s have to be different objects? For example, "abc" and "abc" can be the same array. https://gcc.godbolt.org/z/f4zPTP – RiaD Dec 17 '20 at 22:36
  • or even closer to the form of the OP https://gcc.godbolt.org/z/rY793j (Note that it's reference to actual array passed to the function, no decay yet at this point, so it won't be the explanation) – RiaD Dec 17 '20 at 22:40
  • 2
    @RiaD Edited the answer, great question! – Quimby Dec 18 '20 at 09:24
18

Temporaries persist until the end of the full expression that caused them to spring to life.

[class.temporary]

4 ... Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created.

This is true of all temporaries. This means that in expression #3, assuming its evaluation ends without throwing an exception, both temporaries could have overlapping lifetimes.

With few exceptions (none of which apply here), two different objects within their lifetime will have different addresses.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
1

Some of my previous comments re-posted here as requested:

The really interesting thing is that C++ mandates no concrete encoding on the address of an object. (And it mentions nothing about the address of a function, BTW.) This is natural, because the C++ abstract machine just has no interest on the address in most contexts.

Two different objects are just... not the same one, as they have different identities. The notion of identity is certainly more broadly used, e.g. for lvalues, even though it is also carefully dodged in most contexts. If the difference of identity is interested, the specification just mandates the only allowed manners of the accesses to the objects (e.g. strict aliasing rules), because how many objects here is considered an implementation detail. An address is conceptionally derived from the identity of the objects and it cannot help you to make the difference more obvious.

It is proper to rely on the notion of address to describe the layout among different objects (and subobjects thereof). In this particular context, identity is not enough. This is not the case here (overlapping lifetime, not overlapping storage). The reasoning becomes chaos when addresses are involved. As answered, since the as-if rules are effective, different objects can have the same address when there is no portable way to differentiate the addresses. Also note that addressof and [[no_unique_address]] do not really require to differentiate the addresses (but just identities).

FrankHB
  • 2,297
  • 23
  • 19