CS 70

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?

  • Hedgehog speaking

    Well, we used to write vals[i] and now we're saying *(vals + i), so vals doesn't look like an array any more. It looks like this sort-of ugly thing with a *.

  • LHS Cow speaking

    Okay, can we build on that?

  • Cat speaking

    We're using the memory address of the first element of the array.

  • LHS Cow speaking

    And that means…?

  • Duck speaking

    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 floats, 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”.

  • Horse speaking

    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?

  • LHS Cow speaking

    Context matters. Where the * appears governs what it means.

  • Hedgehog speaking

    How will I ever keep it straight what it means?

  • LHS Cow speaking

    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
  • Duck speaking

    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?

  • LHS Cow speaking

    Let's think this one through…

If we had a normalize function as given above, could we pass in vals as an argument, and would the function have enough information to do its job?

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;
    }
}
  • Hedgehog speaking

    Can I write firstValPtr[i] instead? This syntax with the stars hurts my brain!

  • LHS Cow speaking

    You can, and it'd work just the same. But we'll stick with the * version for now.

  • RHS Cow speaking

    We'll come back to this function in the next lesson page.

When a Pointer Variable Goes Away

  • Dog speaking

    In that code what happens when firstValPtr goes away?

  • LHS Cow speaking

    Pointers are a primitive type, like integers and floats. What happens when an integer goes away?

  • Cat speaking

    It's “destroyed” but probably nothing actually happens.

  • LHS Cow speaking

    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.)

  • Hedgehog speaking

    But the thing it pointed to will still be there, right?

  • LHS Cow speaking

    Yes. (Just like we saw with references.)

When a pointer variable is destroyed, nothing (observable) happens.

Default-Initialized Pointers

  • Horse speaking

    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?

  • LHS Cow speaking

    Yes.

  • Hedgehog speaking

    A pointer that could point anywhere? Sounds scary!

  • LHS Cow speaking

    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.

  • Cat speaking

    So I should always remember to initialize pointer variables.

  • LHS Cow speaking

    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.)