Managing Memory

All programs balance two competing aspects: space needed and time needed

We want our programs to be fast and small to reach the widest audience. When I started building my own computes, back in the 1970’s, the most memory I had in a personal computer was about 4KB, and that cost me around $200 at the time (Cars cost around $2000, so that was a chink of money!)

In building the I35 simulator, we wanted to manage a bunch of vehicles cruising (HA!) down the highway. Probably, your first thought in managing that project was t set up an array of “cars” (whatever those were). We set out to create a class for a Car, then allocated an array to hold enough for the simulation.

There is one huge problem with this approach, and it bites you right away!

How many cars will you need to manage?

500!

Tilt!

As soon as you write down a number like that, invariably your boss will walk up and say “we need 1000”! You say fine, change a line of code, and recompile. Simple. Dumb!

You really never should lock down your code to some arbitrary number you “thought” would do. It is dangerous coding. At the very least, make sure the number you use is agreed to by the entire development team, and the client who wants this code!

Even better, create a better container that can manage a collection of cars in a reasonably efficient way. Unfortunately, efficient means different things at different times.

“The simulator is cool, but it needs more cars.”

The simulator is too slow.”

“The simulator will not run on my tablet!” (Sheesh! Really?)

How much Memory am I using?

In a homework assignment, you explored the many standard data types C++ has available. In that assignment, you learned that a simple integer takes 32-bits, or four bytes of memory.

Let’s do a study of memory usage when we use integers;

ex1.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

int main(void) {
    int a = 5;      // simple integer
    int * b = &a;   // pointer to integer
    int ** c = &b;  // pointer to pointer to integer 
    int *** d = &c; // tiredof this yet?
    std::cout
        << b 
        << " -> "
        << a 
        << std::endl
        << c 
        << " -> "
        << b
        << " -> "
        << **c
        << std::endl
        << d
        << " -> "
        << c
        << " -> "
        << b
        << " -> "
        << ***d
        << std::endl;
}

This crazy code sets up an integer variable. Then it sets up a pointer to that integer, and initializes that pointer to the address of the integer. Finally, I set up a pointer to a pointer to an integer, and initialize that to the address of the first pointer.

(Yeah, this is silly!)

The output shows what we can reach through the pointers. What we are after here is clues about how much memory we allocated, and where things landed in memory.

Running this gives this result:


0x7ffeedc8567c -> 5
0x7ffeedc85670 -> 0x7ffeedc8567c -> 5
0x7ffeedc85668 -> 0x7ffeedc85670 -> 0x7ffeedc8567c -> 5

Those addresses are hexadecimal values, and in the Pentium, an address is a 64-bit number. That means we see 16 characters for each address. (One char equals four bits).

The actual addresses are not important, but the difference between addresses is.

  • a - xxbc

  • b -

From those numbers, I see that my integer is four bytes long, as I expected, and my pointers is 8 bytes long.

I also see that my data is assigned addresses in sequential order. However, the actual addresses seem to be in reverse order, meaning the compiler allocated memory locations from high addresses to low addersses:

according to my declarations. Useful to know!