简体   繁体   中英

C++ : Why reference to stack allocated object treated same as reference to heap allocated object?

First question on StackOverflow so please be indulgent, and don't hesitate to give me feedback on how i could've asked the coming question better. Thanks !

I was wondering,

I know that reference to stack allocated objects take no memory space and have no overhead at exec time, ie :

int x;
x = 0;

and

int x;
int& y = x;
y = 0;

Should be exactly identical when compiled. This sounds obvious to me since the address of b is known at compile time (because it's the same as a, which is known at compile time)

However, i don't know how a C++ compiler handles this if the address is not known at compile time, which happens (as far as i know) when the object being referenced is allocated on the heap, ie :

A& x = *new A();

The value of &x in this context has to be stored somewhere in the compiled code, resulting in a different behavior than if you had a reference to an object allocated on the stack, right ?

So, i know the address of a reference to a stack allocated object has to be an r-value (because of my first example), thus one cannot modify it.

But why one cannot modify the address of a reference to a heap allocated object (since it has to be an l-value as far as i understand it, because of not being able to know it at compile time) ? Why do we only have a "reference" type, and not "reference to stack allocated object" + "reference to heap allocated objects" types, since they are obviously not treated the same way in implementation ?

Thanks for your insights !

The value of &x in this context has to be stored somewhere in the compiled code, resulting in a different behavior than if you had a reference to an object allocated on the stack, right ?

How references work under the hood is up to the implementation. Each compiler is free to make them work however they want. But usually, the easiest solution is to treat T& like a T * const that can never be nullptr and whose address can never be taken. In cases where the referred object is known at compile time, then the reference can be optimized out and it's like it never existed. But this is also true of pointers. It's just that the extra property of not being addressable makes references a little bit easier to optimize out.

What you can be certain of is that the behavior will not be different based on how the referred object was created (dynamically or as a local object). Yes, the cases you show may result in the compiler doing different things, but the behavior of code is not a property of what the compiler does. This reasoning is backwards. The code in defines a strict behavior according to the language standard. Anything the compiler does has to leave that behavior intact, in so far as it respects what the standard says. So references have a behavior, and some uses of behavior will trigger different assembly to be generated but that result will behave the same.

Please note that references have a few extra requirements over pointers that makes the comparison with pointers a bit inaccurate. As an example see this question .

But why one cannot modify the address of a reference to a heap allocated object (since it has to be an l-value as far as i understand it, because of not being able to know it at compile time) ?

The reason you can't change the what a reference refers to is not a technical one. By design, you don't want to be able to change what it refers to even if it would be easy to implement that feature. This is because the immutable nature of references are considered an advantage. If you want a reference that is guaranteed to contain an address and that you can change to refer to other things, the language already provides pointers that can do this for you.

Why do we only have a "reference" type, and not "reference to stack allocated object" + "reference to heap allocated objects" types, since they are obviously not treated the same way in implementation ?

Again, it's not because the implementation treats different use cases of a feature that the interface should expose those differences to the developer. If you have are writing a void mutate(int &) function you want to handle all int s the same way, no matter how they were created or what optimizations the compiler might have come up. There isn't really a use case for a function that only works on stack allocated objects.

Initializations doesn't have to be done at compile-time, the compiler can generate code to do them at run-time (if the right-hand-side isn't known or can be computed at compile-time).

This is what happens when you do

A& x = *new A;

The result of new A isn't known at compile-time, so the compiler will generate code that does the initialization of x at run-time instead.

This isn't really different from other compile-time variable initializations.

A& x = *new A();

How does it work ?

new operator allocates memory from the heap dynamically, after that you've assigned it's reference to x .

int x;
int& y = x;
y = 0;

Here, the reference of int x is stored to y at compile time, because the int x is already created, whereas the object x ( A& x = *new A(); ) is initialized at runtime.

Why reference to stack allocated object treated same as reference to heap allocated object?

No it's not the case. Once you've allocated memory in the heap , it'll be there until program terminates or you manually delete it, whereas the objects/variables allocated in stack/locally get cleared whenever the stack frame of that function/scope is destroyed, so does the references.

More about changing/re assigning references: How can you reseat a reference to make it refer to a different object?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM