ManuelSchneid3r
ManuelSchneid3r

Reputation: 16091

How do (traditional, prior c++11 lvalue-) references work in C++

Pointers are easy. There is some memory which holds an address. To get the (meaningful) value dereferencing returns the value contained by the memory the address points to.

References do somehow something similiar: they hold the "link" to a temporary object. But what happens when I assign or use the reference?

#include <iostream>
using namespace std;
int one(){
    return 1;
}
int main()
{
    const int &rone = one();
    cout << &rone << endl;
    return 0;
}

Why does this work? Is this adress the adress of the temporary object?

sh-4.2# g++ -std=c++11 -o main *.cpp
sh-4.2# main
0x7fff9d2a4e74

Where is this adress pointing to? And if it points to the magic temporary object, why cant I just do the following. (I know well that it is a rvalue and that & accepts only lvalues, but why?)

int main()
{
    cout << &one() << endl;
    return 0;
}

My question goes beyond the programming aspect of c++. It is meant more in a techincal direction how C++ works inside. Primarily I try to understand the rvalue references in the context of the move semantics, which require to understand those traditional references (which I hardly ever use).

Upvotes: 1

Views: 158

Answers (3)

Pixelchemist
Pixelchemist

Reputation: 24946

Variables (objects) and references are "names" (pointers are variables too so the same applies). The programmer uses them to identify certain objects in a program. Note that references do not only reference temporary objects. They may reference any valid object.

Other things (i.e. rvalues) do not have a name and no specified accessible "location" Their handling is a compiler job. In example in x86 (depending on the calling convention), small return values are ususally returned via certain processeor registers.

Consider:

int one() { return 1; }
int main()
{
  one();
  return 0;
}

The return value vanishes here. (The compiler will probably not even call the function at all if it is visible.) If you could take the address of the return value it must be stored somewhere (a location which can be accessed using a valid address).

So in order to be able to take the address of the value returned by one() the value has to be copied from register EAX (where it conventionally resides when cdecl is used) to some location which is "save" to be accessed by a c++ program(mer) .

What would happen if you indeed could access the address of a temporary "directly"?

int const * p = &one(); // would this return "EAX"?
int c = *(p + 1); // ??²

Would you read register EBX in the second line? Or whatever memory location in L1 or L2 or or ?? There is no point in something like this.

The only accessible locations in c++ are on the stack memory and free store ("heap") one of which is used if you create a variable or const reference for a return type.

int main()
{
  int const a = one();
  return 0;
}

Things do not change much from the first example coming here: a has a name and memory assigned, whereas the return value of the function (still) has (and always will have) not. The return value is "computed" into EAX and a is initialized using that integer by copying the register value to a's memory location.

int const & r = one();

Not much of a difference here. The return value is stored at a persistent/accessible memory location and now you can take the address of it using i.e.

int const * p = &r;

Where is this adress pointing to?

I'm not sure but I don't think that standard specifies any location for prolonged temporaries (correct me if I'm wrong). All you need to know is: r refers to a valid object and &r is the address of that object. (You could probably compare the address to some nearby stack variable addresses to find out whether it is likely to be on the stack, too.)

One thing is different between r and a though: The value (stored at the location r refers to) is not necessarily on the stack (although I assume it would end up being on the stack), whereas a from above is definitely on the stack.

Another difference arises when it comes to objects. (At least in theory, although a c++ compiler is allowed to optimize in a way that a and b in the following paragraph aresomehow equivalent).

struct X { /*stuff*/ };
X foo() { return X{ /*.args.*/ }; }
// ...
X const a = foo(); // A
X const & b = foo(); // B

In line B, b is a name for a constant object of type X. (Behind the scenes the compiler will in advance choose some location for the object and return foo directly into that memory in order to avoid copying it.)

In line A, a will be (on the stack and) initialized from the temporary returned by foo via it's copy constructor. (The copy can be elided and an optimizing compiler will most likely construct the temporary directly into a.)

Variable names and references are human identification tokens. A variable / an object is a single instance to which multiple references may exist.

Upvotes: 0

M.M
M.M

Reputation: 141576

Another way to think of reference is alias. Binding a reference provides another name for an object. Before C++11 these two snippets were exactly identical:

int a = 5;
int &b = a;

versus

int b = 5;
int &a = b;

Thereafter, a and b are both names for the same object. (C++11 introduced an asymmetry in that decltype will include the reference in the type returned).

It's not any different in your first example. You bind rone to be a name for the temporary object, and the language rules say that this makes the temporary object's lifetime be extended too.

In your second example there are no references. The address-of operator is a different language element to the symbol for declaring a reference, despite them having the same spelling. It is illegal because the language definition says that the address-of operator cannot be applied to an expression which creates a temporary object.

Upvotes: 6

Emil Laine
Emil Laine

Reputation: 42838

You can bind the temporary to rone because the compiler knows you're not going to modify it. Reading a temporary value is safe, writing to it isn't.

If you remove the const, the code will not compile anymore.

Upvotes: 0

Related Questions