Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are the addresses of two temporaries guaranteed to be different in the same expression?

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?

like image 939
cigien Avatar asked Dec 17 '20 10:12

cigien


Video Answer


3 Answers

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.

like image 137
Quimby Avatar answered Oct 21 '22 00:10

Quimby


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.

like image 45
StoryTeller - Unslander Monica Avatar answered Oct 21 '22 00:10

StoryTeller - Unslander Monica


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).

like image 1
FrankHB Avatar answered Oct 21 '22 00:10

FrankHB