Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where does a value type-variable - which is returned by ref - live? Stack or heap?

I recently heard about the new C# Feature in 7.2, so that we now can return a reference of value type (for example int) or even a readonly reference of a value type. So as far as I know a value type is stored in the stack. And when the method is left, they are removed from stack. So what happens with the int when the method GetX exits?

private ref int GetX()
{
    // myInt is living on the stack now right?
    int myInt = 5;

    return ref myInt;
}

private void CallGetX()
{
    ref int returnedReference = ref GetX();
    // where does the target of 'returnedReference' live now? 
    // Is it somehow moved to the heap, because the stack of 'GetX' was removed right?
}

I'm getting the error

Error CS8168: Cannot return local 'myInt' by reference because it is not a ref local (11, 24)

So why does it not work? Does it not work just because the variable can not be moved to the heap? Is this the problem? can we only return value types by reference if they do not live in the stack? I know that this are two question in one.

First: Where do value type-variables returned by ref live? Stack or heap? (I guess on the heap but why)?

Second: Why can a value type created on the stack not be returned by reference?

So this is able to be compiled:

private int _myInt;

private ref int GetX()
{
    // myInt is living on the stack now right?
    _myInt = 5;

    return ref _myInt;
}

private void CallGetX()
{
    ref int returnedReference = ref GetX();
    // where does the target of 'returnedReference' live now? 
    // Is it somehow moved to the heap? becase the stack of 'GetX' was removed right?
}

If I understand your comments right it is because now the _myInt lives not inside the method GetX and there fore is not created in the stack right?

like image 777
Daniel Avatar asked Mar 19 '18 08:03

Daniel


People also ask

Is reference type stored in heap?

Reference TypesA reference type variable contains a reference to data stored in memory, also known as the heap. The heap is most often used for data that has a longer life. You can have more than one variable point to the same referenced data. Objects are an example of a reference type.

Where is return value stored in stack?

The return value is stored in registers - on x64 it goes in RAX.

Can a value type be on the heap?

Note that a value type can be stored in a stack frame, in the CPU register or even in the heap memory if the value type is contained inside an object, i.e., if it is a part of a reference type.

What is a stack heap value types and reference types?

Reference Type variables are stored in the heap while Value Type variables are stored in the stack. Value Type: A Value Type stores its contents in memory allocated on the stack. When you created a Value Type, a single space in memory is allocated to store the value and that variable directly holds a value.


1 Answers

So as far as I know a value type is stored in the stack.

and thus is the basis of your confusion; this is a simplification that is grossly inaccurate. Structs can live on the stack, but they can also live:

  • as field on objects on the heap
  • as fields on other structs that are (etc etc) a field on an object on the heap
  • boxed on the heap (directly, or via either of the above)
  • in unmanaged memory

You're right, though: if you passed a ref return out of a method, to a local inside a method, you will have violated stack integrity. That's precisely why that scenario isn't allowed:

ref int RefLocal()
{
    int i = 42;
    return ref i;
    // Error CS8168  Cannot return local 'i' by reference because it is not a ref local
}

There are some scenarios when the compiler can prove that even though it was stored as a local, the lifetime was was scoped to this method; it helps that you can't reassign a ref local (to be honest, this check is a key reason for this restriction); this allows:

ref int RefParamViaLoval(ref int arg)
{
    ref int local = ref arg;
    return ref local;
}

Since ref int arg has lifetime that isn't scoped to the method, our ref int local can inherit this lifetime in the assignment.


So what can we usefully return?

It could be a reference to the interior of an array:

ref int RefArray(int[] values)
{
    return ref values[42];
}

It could be a field (not property) on an object:

ref int ObjFieldRef(MyClass obj)
{
    return ref obj.SomeField;
}

It could be a field (not property) on a struct passed in by reference:

ref int StructFieldRef(ref MyStruct obj)
{
    return ref obj.SomeField;
}

It could be something obtained from an onward call as long as the call doesn't involve any ref locals known to point to locals (which would make it impossible to prove validity):

ref int OnwardCallRef()
{
    ref MyStruct obj = ref GetMyStructRef();
    return ref obj.SomeField;
}

Here again note that the lifetime of the local inherits the lifetime of any parameters passed into the onward call; if the onward call involved a ref-local with constrained lifetime, then the result would inherit that constrained lifetime, and you would not be able to return it.

And that onward call could be, for example, calling out to structs held in unmanaged memory:

ref int UnmanagedRef(int offset)
{
    return ref Unsafe.AsRef<int>(ptr + offset);
}

So: lots of very valid and useful scenarios that don't involve references to the current stack-frame.

like image 77
Marc Gravell Avatar answered Sep 28 '22 08:09

Marc Gravell