Search code examples
heap-memoryrefstack-memory

Where does an object reference live and what happens when you pass an object reference by reference?


In the context of C#:

Let's assume the following code ...

public class Foo
    {
        MrObject object1;
        MrObject object2;
        MrObject object3;
        MrObject object4;

        MrStruct struct1;
        MrStruct struct2;
        MrStruct struct3;
        MrStruct struct4;

        int i;

        public Foo()
        {
            object1 = new MrObject(1);  /// Alpha
            object2 = new MrObject(2);  /// Bravo
            object3 = new MrObject(3);  /// Charlie
            object4 = new MrObject(4);  /// Delta

            i = 1;

            InitializeComponent(); // POINT A
            ByValByRef(object1, ref object2);

        }


        public void ByValByRef(MrObject o1, ref MrObject o2)
        {
            o1.foo = 5; // POINT B
            o1 = object3; // POINT C
            o2.foo = 6; // POINT D
            o2 = object4; // POINT E
            return;
        }

My understanding is that at // POINT A - you have created four instances of MyObject. Those four instances live in the heap. - you have four references to those instances. QUESTION 1: Where do they live?

My understanding is that at // POINT B - you have created a new reference to 'Alpha' named 'o1'. That reference is added to and lives on the stack. When the method returns, that reference is removed.
- you have a reference named 'o2'. QUESTION 2: Is this a new reference that is added to and lives on the stack and refers to the 'object2' reference? Or is 'o2' simply an alias maintained in code (and not in memory) and in fact there is still only one reference in memory to 'Bravo' called 'object2' and when this code actually executes, 'o2.foo' is treated as 'object2.foo'?


Solution

  • My understanding is that at // POINT A - you have created four instances of MyObject. Those four instances live in the heap.

    Right. Memory has been allocated on the heap for each object.

    you have four references to those instances. QUESTION 1: Where do they live?

    When you created the objects, you assigned (a reference to) each one to different field on the Foo object. Those fields "live" in the memory allocated for the Foo object, which is almost certainly somewhere on the heap as well.

    My understanding is that at // POINT B - you have created a new reference to 'Alpha' named 'o1'. That reference is added to and lives on the stack. When the method returns, that reference is removed.

    Yes, at POINT B, the parameter o1 also stores a reference to the MrObject that was passed in as an argument. o1 lives on the short-term store, which will in general be "the stack". When the method ends, the space on the short-term store used for o1 will be made available for other use, and the garbage collector will no longer consider o1 as a known live root.

    you have a reference named 'o2'. QUESTION 2: Is this a new reference that is added to and lives on the stack and refers to the 'object2' reference? Or is 'o2' simply an alias maintained in code (and not in memory) and in fact there is still only one reference in memory to 'Bravo' called 'object2' and when this code actually executes, 'o2.foo' is treated as 'object2.foo'?

    And now we get to the tricky part of your question. o2 is a reference of a different sort. It is not a reference to an object on the heap, but rather it is effectively an alias (as you surmise) to the object2 field on the Foo object. o2 still occupies space on the short-term store, though, since it is still an argument to a method.

    As an aside, the fact there is "only one reference in memory to Bravo" is not what you should be worried about. References that are reachable from known GC roots are what determine whether or not a particular object can be safely collected.