Discovering Temporaries
So far, everything should have pretty much been review, but hopefully giving you more practice with the concepts.
But now we'll push the boundaries a bit to see whether our conceptual model works or needs more refinement.
If you closed it
and fork it.
Results from Expressions
Our next example to try is
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 + y);
return 0;
}
If we hand-simulate it, it will produce
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 42
and you can check those results by running our code in the online compiler.
But now let's try a little experiment. We'll change the 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* where = &(x + y);
return 0;
}
Think about whether that highlighed line makes sense. We're going to see what happens in the online compiler in a moment, but before we do, what do you think?
In the online compiler, change the code to match the code above and try to run it (which will cause it to try to compile it). If you've copied the code correctly, you'll get a specific (and probably hard to understand) error message.
When I try to compile the code with clang++
on the CS 70 server, I get the error
main.cpp:19:18: error: cannot take the address of an rvalue of type 'int'
int* where = &(x + y);
^ ~~~~~
1 error generated.
and if I use a different compiler, g++
(in online GDB), I get a similar but different error:
main.cpp: In function 'int main()':
main.cpp:19:21: error: lvalue required as unary '&' operand
19 | int* where = &(x+y);
| ~~^~~
Argh! What's an “rvalue”? Or an “lvalue”, for that matter?
Terminology:
-
An lvalue can be thought of something like a variable that has an associated memory Location.
-
An rvalue can be thought of as a “result” value, or a tempoRary value.
Hay! It looks to me like there's some kind of left/right thing going on.
Yes. If we think of
x = y + z
, the thing on the left needs to be something that has a location in memory, so we can write in a new value, and the thing on the right is the result of an expression. It wouldn't make sense to sayy + z = x
, so the lefthand side and the righthand side are different sorts of thing.These names are a bit arcane, but knowing them helps us understand compiler error messages!
So, for right now, it seems like the results of expressions, like x+y
, don't have a location in memory.
If we replace the line with
int& value = x+y;
we also get errors (different ones!). clang++
says
main.cpp:19:10: error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
int& value = x+y;
^ ~~~
1 error generated.
and g++
says
main.cpp: In function 'int main()':
main.cpp:19:19: error: cannot bind non-const lvalue reference of type int&' to an rvalue of type 'int'
19 | int& value = x+y;
| ~^~
(Notice that this time g++
describes x+y
as “an rvalue” and clang++
calls it “a temporary”—they mean the exact same thing, but this is why it's good to know both terms!)
With this compiler feedback in mind, do you think our next example will work?
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 + y);
return 0;
}
Moment of Truth: What Actually Happens?
Now try the change in the online compiler: change the code to match the example above and try to compile the file.
It works! And the output is basically saying
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 s3, and holds 42
So it turns out that main
has three stack slots, not two, and the third one is holding the result of x+y
. So we could say that x+y
does have a location in memory.
The Truth about Temporaries
Whenever C++ invokes any kind of function (or operator) that returns a value, or we directly invoke a constructor (like we did in the Before You Start section), if we're not putting the resulting value directly into a known memory location (such as a named variable), the program stores it as a “temporary” value. The returned value is, in essence, a short-lived, unnamed variable that exists only for the duration of that line of code.
Much of the time, we can just ignore the fact that temporaries exist. But sometimes it is useful to know that they do. When we want to model temporaries in our diagrams, we'll give them fake names like _temp1
, _temp2
, and so on.
So we can model a line like
passByRef(x+y);
as if it were
{
int _temp1 = x+y;
passByRef(_temp1);
}
If temporaries exist, why can't I say
&(x+y)
?Mostly to stop people who are super confused from making mistakes. Our
passByRef
function showed that we can actually find out where in memory this intermediate result is stored.
Time for You to Experiment!
One reason we use the online compiler is to allow you to try some things without having a build environment ready to use. Now's your chance! Here are some ideas:
- See if memory gets reused for temporaries by trying
passByRef(x+y);
passByRef(x*y);
- See if
const
matters by removing theconst
frompassByRef
or adding it somewhere else; for example,
const int& v = x+y;
cout << "main's v is at address " << &v << ", and holds " << v << endl;
Storage Reuse for Temporaries
FWIW, if you tried
passByRef(x+y);
passByRef(x*y);
you might think you know what's happening, but the truth is that “it depends”, and that's a good thing to remember about any experiment you try. You might get some insights, but it may also be that you're not seeing the full picture. For example, here's that code example compiled two different ways on the CS 70 server:
cs70 SERVER > clang++ -o model-example model-example.cpp
cs70 SERVER > ./model-example
main's x is at address 0x7ffcceef80f8, and holds 23
main's y is at address 0x7ffcceef80f4, and holds 19
passByRef's r is at address 0x7ffcceef80f0, and holds 42
passByRef's r is at address 0x7ffcceef80ec, and holds 437
cs70 SERVER > clang++ -O -o model-example model-example.cpp
cs70 SERVER > ./model-example
main's x is at address 0x7ffeb2691c90, and holds 23
main's y is at address 0x7ffeb2691c8c, and holds 19
passByRef's r is at address 0x7ffeb2691c94, and holds 42
passByRef's r is at address 0x7ffeb2691c94, and holds 437
In the first example, we compiled without optimization and the space for the temporary wasn't reused, but in the second case, where we compiled with (some) optimization (-O
), the space was reused.
Only const
References can Bind to Temporaries
If you tried messing with const
, you might have discovered that if you change passByRef
to take a non-const
reference, like this:
void passByRef(int& r) {
cout << "passByRef's r is at address " << &r << ", and holds " << r << endl;
}
then the code won't compile. The compiler will give you an error message like
main.cpp:19:19: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
19 | passByRef(x+y);
| ~^~
This is one case of C++ trying to help you avoid mistakes. If you could bind a non-const
reference to a temporary, you could then try to modify the temporary through that reference, which would be very confusing since the temporary is about to go away. In most situations, it would be a strange thing to do to go to a lot of effort to make updates to an object only to turn around and throw away all that work immediately afterwards.
So I get that temporaries exist, but do I need to draw out every temporary in my memory diagrams?
Most of the time, no. If you're just trying to understand a piece of code, you can usually ignore temporaries. But in a case like
passByRef(x+y)
, we've written code that specifically interacts with a temporary, so we need to include it in our diagram.Remember that the goal of memory diagrams is to help you understand what's going on in your code. If you can do that without drawing every temporary, great! But if you need to include them to understand something, go ahead and do so.
(When logged in, completion status appears here.)