A "Feild" Guide to Programming with C++
Introductory programming notes by Henry FeildChapter 12: Pointers and Dynamic memory
Important takeaways
- declaring pointers: type *name
string *city;
int *age;
float *area;
- assigning a memory address to a pointer: &variable or new type (dynamic memory)
city = &myCity;
age = new int;
area = new float;
- accessing the memory that a pointer points to (dereferencing the pointer): *name
cout << *city << endl;
*age = 10;
sqFootage = *area;
- aways delete memory allocated using
new
when finished using it:- delete age;
- dynamic arrays:
- use memory allocated from the heap
- the size can be determined at run time
- use
new type[size]
to allocate - use
delete[]
to deallocate - multi-dimensional arrays are similar; see examples below
Contents
Pointers
Up until now, we have used variables to store a value, like a number or string. If we have two variables and we want them to be set to the same value, we have to assign that value to each variable independently. If we change the value of one variable after the assignment, the other variable's value is left unchanged. For example, consider the following lines of code:
This code creates two int
variables: x
and
y
. We set the value of x
to 10. We then copy that
value to y
in line 2. At the completion of line 2, the value 10 is
stored in both x
and y
. On line 3, we update the value
of x
to be 20. y
remains unchanged and still stores
the value 10. Line 4 causes 20, 10
to be printed to the console.
What if we want x
and y
to be linked together so that
if we update one, we update both? We can do that using pointers. Specifically, pointers allow us to store the
address of a memory location. So we can store a value in a regular variable,
like 10 in the integer variable x
, then create a pointer to an
integer variable named y
, and finally assign the address of
x
to y
(this is what links them). We can then use
special syntax to access the value pointed to by y
. Here's an
example:
Note that we use the asterisk two ways. First, in the declaration of a pointer, we need an asterisks before the variable name. Second, we can dereference the pointer, i.e., specify the value that the pointer points to, by also placing an asterisks before the variable name. We often refer to dereferencing as "following" the pointer. To get the address of any variable, we use an ampersand prior to the variable name.
Here's what the memory looks like:
Variable | Mem. Addr. | Binary value | Decimal value |
---|---|---|---|
int x | 1 | 00000000 | 10 |
2 | 00000000 | ||
3 | 00000000 | ||
4 | 00001010 | ||
int *y | 5 | 00000000 | 1 |
6 | 00000000 | ||
7 | 00000000 | ||
8 | 00000000 | ||
9 | 00000000 | ||
10 | 00000000 | ||
11 | 00000000 | ||
12 | 00000001 |
In this table, x
is given memory address 1. Suppose we are on a
system where it takes up a total of 32 bits, or four bytes, starting at address
1. If our memory is set up in one byte slots, then we need four slots (addresses
1&ndah;4). When we assign 10 to x
, that is stored as a 32 bit
number spread across the four slots (I'm showing the decimal number just so it's
easy to read). When we create the pointer to an int called y
, on a
64-bit operating system we need to assign a total of eight slots, because an
address can require up to 64-bits. When we assign the address of
x
—1—to y
, we store that address as a
binary number spread across the eight slots. As an aside, the type of a variable
tells C++ how many memory slots it needs to look at for that variable. For a
pointer, the type of pointer tells C++ how many memory slots the thing the
pointer points to takes up. So an int pointer points to four slots of memory.
Dynamic memory
While pointers allow us to link two variables, we use them mostly with dynamic memory. First, lets talk about how a program sits in memory. Each program consists of four segments of memory: the compiled program code, global constants, a segment called the stack for local variables and function parameters, and finally, a segment called the heap from which dynamic memory can be allocated.
The stack segment is of limited size and because of that, if your program stores lots of data in memory, it is beneficial to allocate memory from dynamic memory. For right now, let's not worry so much about why, and worry more about how. The first thing is, we need to make use of pointers. So, if we want to allocate space for an integer, the first thing we need is an integer pointer:
Now we need to assign this pointer the address of a block of memory allocated
from the heap. To do that, we will use the new
keyword, like this:
This says, "Create a new int pointer called x
, allocate memory for
an int in the heap, and assign the address of that memory to x
". We
can assign a value to that memory by dereferencing the pointer and setting the
value:
Dynamic memory is great, but it is important that for every piece of memory we
allocate, we also deallocate it. That is, we need to
free the memory so that another process may use it. We do that using the
delete
keyword. It is important to do so only after you are
finished using the memory. Here's a complete example of allocating, setting, and
then deallocating dynamic memory:
Dynamic arrays
In the long run, static arrays (which we learned about earlier) will hold us back since we have to know their size at compile time. Dynamic arrays do not have this same constraint, and they should be easy now that we know about pointers!
Dynamic arrays are arrays that we allocate memory for in the heap using the
new
keyword. The process is only slightly different than what we
did with static arrays.
Dynamic 1D arrays
Let's start with 1D arrays. Here's an example:
First of all, our array myArray
is an int
pointer—just like before! But the memory it points to isn't just for an
int, it's for three ints. We say this by using square brackets after the type
when we allocate memory with the new
keyword: new
int[3];
.
Next, we don't need a * to dereference our array—the square brackets are
doing that for us automatically. So, myArray[0]
will access the
first thing in the array, just like with a static array.
Finally, we cannot use the regular delete
keyword to deallocate our
array. Rather, we need to use a special version: delete[]
. And
that's it!
So we said that dynamic arrays are nice because we can get the size from the user, right? Here's a demonstration of allocating space for an array based on what the user requests:
This program reads in the size of the array from the user, creates a dynamic array of that size, then prompts the user to enter the value that should be stored at each index. Running this gives the following (the user's input is in orange):
$ ./arrays How many entries should the array be? 4 Please enter a number to store at index 0: 948 Please enter a number to store at index 1: 32 Please enter a number to store at index 2: 39 Please enter a number to store at index 3: 22342
You can pass dynamic 1D arrays to functions just like how we learned to pass static 1D arrays here.
Dynamic multi-dimensional arrays
Declaring and initialize dynamic multi-dimensional arrays is similar to 1D arrays, except that each dimension needs to be handled separately. In addition, passing dynamic multi-dimensional arrays is not the same as their static counter parts! The example below demonstrates how they compare.
(Back to top)