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++
, 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++
, 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”.)
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;
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 in our Docker image:
cs70 DOCKER > clang++ -o model-example model-example.cpp
cs70 DOCKER > ./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 DOCKER > clang++ -O -o model-example model-example.cpp
cs70 DOCKER > ./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.
(When logged in, completion status appears here.)