Pointers and Dynamic memory
Contents
Pointers
Up until now, we have used variables to store a value, like a number or string. If we have two variable 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 only one variable, 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.
What if we want x
and y
to be tied together, so that if we update one, we can update the other? 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
. We can then use special syntax to access the value pointed by 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. 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. Because it is an int, 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 who 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 sometimes it is beneficial to allocate memory from dynamic memory. For right now, lets 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: