CS 70

Memory Model Practice — Passing Copies

Exploring What Really Happens

  • Goat speaking

    Is all this “memory diagram” stuff what really happens in C++?

  • Bjarne speaking

    The C++ standard specifies an abstract model, not a real physical machine, and leaves many details unspecified so that they can best match a particular kind of machine where C++ runs.

  • LHS Cow speaking

    But the real answer is yes. We do leave out a few details (which you'll see if you take CS 105), but the memory diagrams we're learning to draw are intended to help you understand what's happening when you run real code on a real machine.

In this part, we'll look at a small program and see what it does according to our model, but also actually run it and look at the results. Doing so will give us some practice with drawing memory diagrams, but we'll also see something of what really happens when we run programs.

The Program

We'll trace through the execution of this program. We're asking you to grab a piece of paper (or a tablet you can draw on) and make some memory diagrams.

#include <iostream>

using namespace std;

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

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

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;

    // <--- Draw diagram here!

    return 0;
}

Using a memory diagram, trace through the execution of the program up to the point where it says:

// <--- Draw diagram here!

Print out the memory addresses symbolically (e.g., s1, s2) rather than trying to simulate strange-looking hexadecimal numbers C++ would actually print.

  • RHS Cow speaking

    There's a help page with all the rules for memory diagrams.

What will this code print?

Here's a video to show how we got there…

Our hand simulation will print

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

Running the Code for Real!

But now let's confirm it by actually running the code.

  • LHS Cow speaking

    Here's a tip: Keep this tab open! We'll use this code as a running example, and it may be a better way to refer back to the code than scrolling around on this page.

What does it print? (Copy the output from the bottom of the window and paste it here. Exclude the bit about it completing with exit code 0.)

When we run the code, we see something like

main's x is at address 0x7ffefbb8cb78, and holds 23
main's y is at address 0x7ffefbb8cb74, and holds 19
(example-1 does nothing else.)

Your memory addresses probably won't be the same, but notice that they're big hexadecimal numbers. Also notice that y's address is smaller than x's address.

Is this output consistent with what the CS 70 execution model suggested?

We can see that that s2 has a smaller address than s1, and that the addresses take object size into account (e.g., an int is four bytes).

Hopefully you can see why it's easier to just write s1 and s2!

Parameters that are Copies of the Arguments

Let's look at the first function, passByVal:

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

Notice that the parameter v is declared as an int. v is a distinct int variable in its own right, so when we pass in an int as an argument, v will hold a copy of that int.

Terminology: We say that the v parameter is passed by value (a.k.a. passed by copy), because an entirely new value (the copy) gets stored in v.

To explore what happens when we call passByVal, we'll add these lines to main, as shown:

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;

    passByVal(x);
    passByVal(y);

    return 0;
}

Rerun the code by hand and figure out what it will say. Don't compile any code yet—the goal is to see how you're doing with drawing memory diagrams.

What would the code print, according to our hand simulation? (Just give the two new lines.)

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

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
passByVal's v is at address s3, and holds 23
passByVal's v is at address s3, and holds 19
  • Duck speaking

    I used s3 and s4, is that okay?

  • LHS Cow speaking

    No. The stack slots get reused.

  • RHS Cow speaking

    When the first call to passByVal returns, the stack space is deallocated, so it's free to be allocated again for the next function call.

  • LHS Cow speaking

    Because it's the same function and thus works the same way, the new v variable ends up in the same spot the old one was.

Running the Code for Real!

Let's check to see if our hand simulation matches reality. Edit the online code to match the version above and run it.

Copy and paste the two output lines about passByVal

When I run it, I see something like

main's x is at address 0x7fffc850fce8, and holds 23
main's y is at address 0x7fffc850fce4, and holds 19
passByVal's v is at address 0x7fffc850fcbc, and holds 23
passByVal's v is at address 0x7fffc850fcbc, and holds 19

This output is consistent with what we'd expect.

  • RHS Cow speaking

    Keep the OnlineGDB tab open! The next page continues with the same example.

(When logged in, completion status appears here.)