Past-the-End Pointers
There is an alternative to passing a pointer to the first element of an array and its size, we could instead pass a pointer to the first element of an array and a pointer to the “end” array.
If we have an array declared as:
float vals[TEMPERATURE_SAMPLE_COUNT];
// (and appropriately initialized by reading from a file)
Notice that
vals
decays to a pointer to the first element of the arrayvals + 0
also gives a pointer that points to the first element of the arrayvals + TEMPERATURE_SAMPLE_COUNT - 1
gives a pointer that points to the last element of the arrayvals + TEMPERATURE_SAMPLE_COUNT
gives a pointer that points to just beyond the last element of the array
It might seem counterintuitive, but it's almost always way more convenient to have “past-the-end” pointers than “last-element” pointers.
Let's look at a revised normalize
function that uses this approach.
void normalize(float newmax, float* firstValPtr, float* pastEndPtr) {
float maxval = *firstValPtr;
for (float* p = firstValPtr; p < pastEndPtr; ++p) {
if (*p > maxval) {
maxval = *p;
}
}
for (float* q = firstValPtr; q < pastEndPtr; ++q) {
*q = *q / maxval * newmax;
}
}
Notice that the code in the loop is simpler than before. Instead of saying *(firstValPtr+i)
we're now just saying *p
.
I think it's more efficient, too! We're avoiding two addition operations every time around the loop!
Possibly. But compilers are good at optimizing code, so we can't really make judgements of that kind without experimental data to back up our claims.
Meh! It might not be more efficient for the generated code, but it's definitely more efficient for me, the programmer! It's less typing to say
*p
than*(firstValPtr+i)
or evenfirstValPtr[i]
.
Calling Our Function
We'd call this function by writing
normalize(height, vals, vals+TEMPERATURE_SAMPLE_COUNT);
C++ also provides an alternative way of writing that code. We can write it as
normalize(height, std::begin(vals), std::end(vals));
Indeed.
std::begin(vals)
just returnsvals
(the address of the first element), andstd::end(vals)
returns the address that lies past-the-end of the array.Although how the compiler achieves that, for any array of any type, isn't something we'll delve into at this time.
Exercise
Trace through the execution of this code with a memory diagram, include the execution of the normalize
function.
int main() {
float stuff[4]{5.0f, 1.0f, 4.0f, 20.0f};
float desired_max = 100.0f;
normalize(desired_max, stuff, stuff+4);
return 0;
}
Before we do that, is
float stuff[4]{5.0f, 1.0f, 4.0f, 20.0f};
the same as
float stuff[4] = {5.0f, 1.0f, 4.0f, 20.0f};
or not? And also, what does that
f
mean at the end of the numbers?Yes, they're the same. The second version is the older C-style way of saying it. Both syntaxes are widely used in practice.
Saying
5.0f
means “this is afloat
literal for the number 5—if we'd just said5.0
it would be adouble
literal, and it would have to be converted to afloat
to put it in the array.
This video traces through the execution of the code and shows the memory diagram at each step.
Here's the final memory diagram:
(When logged in, completion status appears here.)