What's the C++ Type for a Memory Address?
Let's look back at the code for normalizing the data values from Homework 2. In this version, we've used the more long-winded syntax for array access so that we can better see what's going on.
float biggest_val = *vals;
for (int i = 0; i < TEMPERATURE_SAMPLE_COUNT; ++i) {
if (*(vals + i) > biggest_val) {
biggest_val = *(vals + i);
}
}
for (int i = 0; i < TEMPERATURE_SAMPLE_COUNT; ++i) {
*(vals + i) = *(vals + i) / biggest_val * height;
}
We wanted to be able to write a helper function. Does this version of the code give us any ideas?
Well, we used to write
vals[i]
and now we're saying*(vals + i)
, sovals
doesn't look like an array any more. It looks like this sort-of ugly thing with a*
.Okay, can we build on that?
We're using the memory address of the first element of the array.
And that means…?
We don't need to pass whole arrays around, we could pass addresses! Only I don't know how.
Pointer Types
When we have the memory address of the first element of an array of float
s, we call that a pointer to a float
, and we write the type as
float* start = vals;
Notice the *
in the type.
We read the this type (from right to left!!) pronouncing *
as “is a pointer to”, so in this case “start
is a pointer to a float
”. You can also think of it as saying “start
is a memory address for a location in memory where we'll find a float
”.
Hay! Hold your horses! I thought
*
meant something else, to read the value from that memory, now it means a type thing. How can it mean two things?Context matters. Where the
*
appears governs what it means.How will I ever keep it straight what it means?
It's not as bad as the ambiguity in English! The rules for understanding which context we're in are fairly simple. Let's remind ourselves a bit about types and values.
Types and Values
Types and values are different things, and used in different places.
Types
For example, this declaration is a context where we've written a type three times, the type int
.
int area(int width, int height);
Values
But the code below has an expression where we compute a value (and send it to the cout
output stream).
cout << area(40,30);
We can actually say there are five values in this expression, 40, 30, the result of the function call area(40,30)
, the value of cout
, and the result of the <<
operator (which is actually returned by <<
and then just thrown away unused). But we'll focus on the first three.
Types and Values Together
So, when reading code, we need to keep in mind whether we're looking at a type, or looking at a value. Check out this code:
int triangle[3] = {3,4,5};
int* secondVertexPtr = triangle+1; // * is in a type
int secondVertexValue = *(triangle+1); // * is in an expression
This code has two disinct types, int
and int*
. And several values, including 3
, triangle+1
and *(triangle+1)
.
In summary,
In a Type Context | In a Value Context | |
---|---|---|
* means |
pointer to | indirection operator |
* written |
after the type | in front of the address |
Okay, I think I get it. So now I can write a function
void normalize(float* firstValPtr);
and then I can call
normalize
in each of my two functions?Let's think this one through…
A Usable Normalize Function
In the code below, we've created a normalize
helper function that takes in all the information it needs to do its job.
void normalize(float newmax, float* firstValPtr, size_t valCount) {
float maxval = *firstValPtr;
for (int i = 0; i < valCount; ++i) {
if (*(firstValPtr + i) > maxval) {
maxval = *(firstValPtr + i);
}
}
for (int i = 0; i < valCount; ++i) {
*(firstValPtr + i) = *(firstValPtr + i) / maxval * newmax;
}
}
Can I write
firstValPtr[i]
instead? This syntax with the stars hurts my brain!You can, and it'd work just the same. But we'll stick with the
*
version for now.We'll come back to this function in the next lesson page.
When a Pointer Variable Goes Away
In that code what happens when
firstValPtr
goes away?Pointers are a primitive type, like integers and floats. What happens when an integer goes away?
It's “destroyed” but probably nothing actually happens.
Exactly. (But, you can't be sure the old value will still be there—at least in theory, it could set all the bits to zero or something.)
But the thing it pointed to will still be there, right?
Yes. (Just like we saw with references.)
When a pointer variable is destroyed, nothing (observable) happens.
Default-Initialized Pointers
Hay, wait a minute! If a pointer is a primitive type, if we don't specify how to initialize it, will it have an indeterminate value?
Yes.
A pointer that could point anywhere? Sounds scary!
Yes, in practice, yes. C++ says that default-initialized pointers are considered “invalid” and you aren't allowed to apply
*
to them. If you break this rule, it's undefined behavior.So I should always remember to initialize pointer variables.
That's a good rule of thumb!
If a pointer variable is default initialized, it will have an indeterminate value. C++ says that all indeterminate pointer values should be considered invalid, and it is “against the rules” to try to access the memory that an invalid pointer points to (via the *
operator). As we'd expect for C++, if you write a buggy program that breaks this rule, the result is undefined behavior. (In practice, an invalid pointer often points to a memory area that doesn't even belong to our program, which causes the operating system to kill it with a “Segmentation fault”, or is improperly aligned for the kind of value we're reading and causes a “Bus error”.)
(When logged in, completion status appears here.)