CS 70

Memory Model Practice — Passing References

If you closed it

and fork it.

Parameters that are References to the Arguments

Okay, now let's look at the second function, passByRef:

void passByRef(const int& r) {
    cout << "passByRef's r is at address " << &r << ", and holds " << r << endl;
}

Notice that here, the type of the parameter r is const int&, which we read as “a reference to a constant int”.

Terminology: We would say that r is passed by reference (or, more specifically, passed by read-only reference).

Now we'll replace both our calls of passByVal in main to be passByRef:

int main() {
    int x = 23;
    int y = 19;
    cout << "main's x is at address " << &x << ", and holds " << x << endl;
    cout << "main's y is at address " << &y << ", and holds " << y << endl;

    passByRef(x);
    passByRef(y);

    return 0;
}

Rerun the code by hand and figure out what it will print.

  • Goat speaking

    Can't I just edit the code, then compile and run it to find out?

  • LHS Cow speaking

    That's a tempting shortcut, but by thinking it through first you're building your own skills. Taking shortcuts won't help that process.

What would the code print, according to our hand simulation?

main's x is at address s1, and holds 23
main's y is at address s2, and holds 19

(give the final two lines)

Our hand-execution says we should expect output like

main's x is at address s1, and holds 23
main's y is at address s2, and holds 19
passByRef's r is at address s1, and holds 23
passByRef's r is at address s2, and holds 19

Remember that r ends up just being another name for one of our existing variables. In our model, passByRef doesn't need any stack space.

Run It for Real

Change the code in the online compiler and run it. You'll see that the result is consistent with our model.

Using the Heap: A Single Item

Now, let's use new. We'll change our test code to

int main() {
    int x = 23;
    int y = 19;
    cout << "main's x is at address " << &x << ", and holds " << x << endl;
    cout << "main's y is at address " << &y << ", and holds " << y << endl;

    int* p = new int{54};
    cout << "main's p is at address " << &p << ", and holds " << p << endl;
    passByRef(*p);
    delete p;

    return 0;
}

Hand-simulate this code; a memory diagram will help.

  • Duck speaking

    I think I can just skip the memory diagram.

  • LHS Cow speaking

    It's tempting to take shortcuts and run it in your head, but that approach makes mistakes more likely.

  • RHS Cow speaking

    Using diagrams to help you think through tricky bits of your code will help you avoid some bugs completely, and also help you figure out ones that happen anyway, instead of just tinkering with your code, hoping that you'll fix the bug along the way.

Tell us about the history of p: When was it allocated? When was it initialized? When was it destroyed? When was it deallocated? What about the value it held?

Here's one way of giving the answer:

  • p was allocated at the start of main
  • p was initialized at the line with new, and then, afterwards,
    • p held a pointer to an int on the heap that
      • was initialized to 54, and then
      • was destroyed and deallocated by the call to delete.
    • p kept pointing to that deallocated memory afterwards.
  • p was destroyed and deallocated at closing curly brace of main.
  • Hedgehog speaking

    So delete doesn't destroy the variable, it destroys the thing it points to?

  • LHS Cow speaking

    Yes, and deallocates it, too. delete needs a pointer (the one that came from new) to do its work.

  • Dog speaking

    So how do I destroy a variable? Is it like this?

    delete &v;
    
  • LHS Cow speaking

    No–o–o–o!! Named variables live on the stack and are destroyed and deallocated automatically. We only use delete for things on the heap—pointers that originally came from new, not pointers to data on the stack.

  • Bjarne speaking

    Your code would compile, because &v is a pointer and delete expects a pointer, but it would have undefined behavior, because you're breaking all the rules.

Hand-simulating this code, we'd produce the following output (I arbitrarily picked h101 for the heap location):

main's x is at address s1, and holds 23
main's y is at address s2, and holds 19
main's p is at address s3, and holds h101
passByRef's r is at address h101, and holds 54

Try Running the Code

You can confirm this result by editing the code in the online compiler to match our example and running it.

Using the Heap: An Array

Now let's use the array version of new. We'll change our test code to

int main() {
    int x = 23;
    int y = 19;
    cout << "main's x is at address " << &x << ", and holds " << x << endl;
    cout << "main's y is at address " << &y << ", and holds " << y << endl;

    int* q = new int[2]{13, 29};
    cout << "main's q is at address " << &q << ", and holds " << q << endl;
    passByRef(*q);
    passByRef(*(q + 1));
    delete[] q;

    return 0;
}

It behaves very similarly. If we hand-simulated a run, it would produce

main's x is at address s1, and holds 23
main's y is at address s2, and holds 19
main's q is at address s3, and holds h120
passByRef's r is at address h120, and holds 13
passByRef's r is at address h121, and holds 29
  • Horse speaking

    Hay, do those heap locations for the array need to be right next to each other?

  • LHS Cow speaking

    Yes. Arrays occupy contiguous memory.

If we run the code in the online compiler, we'll see similar results. For example,

main's x is at address 0x7ffda41b7408, and holds 23
main's y is at address 0x7ffda41b740c, and holds 19
main's q is at address 0x7ffda41b7410, and holds 0x55d1e6aeb2c0
passByRef's r is at address 0x55d1e6aeb2c0, and holds 13
passByRef's r is at address 0x55d1e6aeb2c4, and holds 29
  • Hedgehog speaking

    My numbers aren't exactly the same. Is that okay?

  • LHS Cow speaking

    Yes. There is no guarantee that the stack or heap will be in exactly the same place on different runs of a program, even on the same machine.

  • Cat speaking

    I notice that the stack looks very far away from the heap—the address is much smaller.

  • LHS Cow speaking

    That's right. On many systems, you can easily tell just from what the address looks like whether it's a stack address or a heap address.

(When logged in, completion status appears here.)