C: Pointers and Memory Allocation
NOTE: This page discusses pointers in the context of plain C programming, not C++. It provides the fundamental explanation of pointers. If you are focused on C++, read this page up until the section on memory allocation, and then go to this page.
Pointers
Large-scale programming problems generally require a programmer to move beyond simple variables and start dealing with collections of data. Usually the first way that programmers do this is with arrays. Arrays are a very convenient way to declare and use collections of data. But arrays are static; they cannot grow or shrink to the size that’s needed for each particular use of the program.
This is where pointers come in. Pointers (along with memory allocation) allow you to create programs that can use as much memory as they need (and not more than they need).
A pointer is fundamentally a reference to some other piece of data. It is not the data itself. In C, the value in a pointer that represents the reference is often called an address, since computer memory is accessed using addresses that denote which memory location is being accessed. Note that the above is a generic use of the word “reference”, not the particular use in C++. C++ has both pointers and references as two different features.
A special value of 0 is called the null pointer, also known as
NULL
and “nil”. If a pointer has the value of 0, it is
known to NOT be pointing at anything. Many standard C header
files define the constant NULL
, and C++ has the keyword nullptr
for this. But many programs will often use 0
for this, too, so
you should be able to recognize all of these.
Declarations
A pointer is declared by using the *
notation added to
an existing datatype. For example, “int*
” is a pointer
to an integer, and “double*
” is a pointer to a double.
So, the declaration
int* ip;
declares a variable named ip
that is a pointer to an
integer. C and C++ ignore the spaces around *
, and so you will
see these variations:
int* ip;
int * ip;
int *ip;
All of the above declare ip
to be a pointer to an integer, but the
first form is preferable.
Worse yet, you can combine data and pointer variables in a declaration like this:
int x, *ip, y;
In the above, x
and y
are plain integer variables, while ip
is a pointer variable. For your own sanity, do not do this!
What does a pointer variable declaration mean?
In short it means exactly what is says. Remember what a variable is: a named area of memory that can hold a value. So a pointer declaration declares a variable that can hold a pointer value. That’s it! Nothing else!
So, declaring int* ip;
does not create any space to hold an integer!
You do not automagically get space to hold the data a pointer might
point to, all you get is space to hold a pointer itself!
Never forget this.
Creating a valid pointer value
One way to create a pointer value is to use the &
operator
on a variable name. This operator is known as address-of, because
that’s what it does – it provides the address of (in other words, a
pointer to) the variable it is applied to. So, the code
int *ip;
int x;
x = 42;
ip = &x;
will assign 42 to x
, and will assign to ip
a pointer to x
.
Using a pointer
The basic operation that you use a pointer with is the
dereferencing operation. In C/C++ this is the *
operator. The *
follows a pointer and returns the value in the memory
location that is being pointed to. At the end of the above block,
*ip
would refer to the same memory location that x
refers to, and would give us access to the value 42. So,
printf("%d %d\n", x, *ip);
would print “42 42
”, since both refer to the same location,
and hence the same value (that is stored in that location).
You can also say
*ip = 37;
and this would update the location pointed to by ip
to
contain the value 37. Since this is the same location as the
variable x
refers to, it too now contains 37.
Pointers and arrays
This is one of the key concepts, and most confusing, in C and C++. It is known as the duality of pointers and arrays. Make no mistake, a pointer is not an array – but if it points to an array, you can pretend it is!
If you make a pointer point to the first element of an array, you can use array indexing notation on the pointer itself!
int *ip;
int arr[4] = {7,4,9,2};
ip = &(arr[0]); // set ip to point to arr[0]
ip[2] = 42; // this will set arr[2] to be 42!
Important: both *
and []
are dereferencing operations.
In the above we do not use *ip[2]
to access the third array element,
because ip[2]
already means “dereference and move forward two values”.
This should bring a question to mind: if []
is a dereferencing
operation and we use it on an array name, does that mean we could
do *arr
? The answer is yes! You can treat the name of an array
as a constant pointer, but mostly you should not.
Memory allocation and deallocation (plain C)
Pointers are not really very useful when simply pointing to pre-declared variables. What really makes them useful is that you can on-the-fly allocate new variables out of unused memory. This allows a program to deal with variable amounts of memory.
The basic memory allocation function is malloc()
. Its
function prototype is:
(void *) malloc(size_t numbytes);
What this means is that it takes a single argument that is the
number of bytes you want to allocate (size_t
is usually
the same as unsigned int
), and it returns a pointer to
that new memory space. Since malloc
does not know what
type of pointer you want, it returns a pointer to void
,
in other words a pointer to an unknown type of data.
The typical way that you use malloc
is with a built-in
operator called sizeof()
. sizeof()
returns the
size in bytes of a specific type, which is provided as a parameter.
So, all put together we can do something like:
int *ip;
ip = (int *) malloc(sizeof(int));
We are calling malloc and asking it to allocate enough bytes of memory to hold one integer value (on most computers these days, this would be 4), and we are taking the pointer returned from malloc and casting it to be a pointer to an integer.
Allocating individual integers isn’t all that useful either. But
malloc()
can also allocate arrays. The following example demonstrates this:
int *ip;
ip = (int *) malloc( sizeof(int)*10 ); // allocate 10 ints
ip[6] = 42; // set the 7th element to 42
ip[10] = 99; // WRONG: array only has 10 elements
// (this would corrupted memory!)
Freeing memory, and memory leaks
Suppose we had the following code:
int *ip;
ip = (int *) malloc( sizeof(int)*10 ); // allocate 10 ints
... (use the array, etc.)
ip = (int *) malloc( sizeof(int)*100 ); // allocate 100 ints
...
This is perfectly valid C code – you can always assign a new value to a pointer variable, so calling malloc again to allocate new memory is legitimate. But what happens to the first allocated memory of 10 ints?
The answer is that it is lost forever (at least until the program ends). This is called a memory leak. If you allocate memory, and then change the pointer that was pointing to that memory (without making any copies), then your program can no longer access that memory, even though it is still allocated.
The solution to this is that C and C++ require you to free the allocated memory when you are done with it. So the code above should be:
int *ip;
ip = (int *) malloc( sizeof(int)*10 ); // allocate 10 ints
... (use the array, etc.)
free(ip); // de-allocate old array
ip = (int *) malloc( sizeof(int)*100 ); // allocate 100 ints
...
This program does not leak memory. It correctly frees the old allocation before allocating the new array.
In Java, there is no free
function. Java automatically cleans
up an allocated memory. This is called automatic garbage collection.
But C and C++ do not have this. You are responsible for freeing any
and all memory that your program allocates.
Pointers arithmetic
Ok, so it gets worse. You can also add and subtract integer values
from pointers to move them in memory! This is called pointer arithmetic
and it has the same effect as array indexing, except it does not
automatically imply dereferencing. So if p
points to an array,
p+2
points to the third element of the array, and *(p+2)
is
equivalent to p[2]
.
Pointers can use array indexing notation, or pointer arithmetic notation. So can arrays. An array name is essentially a constant pointer.
int *p;
int a[10];
p = (int*) malloc(sizeof(int)*20);
p[2] = 100; // set 3rd element in alloc'd array to 100
*(p+2) = 100; // same thing
a[2] = 200; // set 3rd element in regulary array to 200
*(a+2) = 200; // same thing
Pointers and arrays are different, but they are accessed similarly if the pointer is being used to access a block of values. However,
- the array declares a block of some datatype while a pointer only declares space for itself (the data area needs malloc’d)
- The address of the array (i.e., a pointer to it) is not stored anywhere; the compiler figures it out while it is compiling your program, so when you use the array name as a pointer, you are essentially using a constant number as an address.
Pointers and arrays as arguments
The one place where pointers and arrays become almost exactly synonomous is when they are used as parameters to functions. This is because arrays as arguments pass only the address of the array to the function. In other words, arrays as passed as pointers!
In C, most values are passed using call-by-value, which means that the function gets a copy of the value. If the function modifies the parameter, the original variable used as a parameter does not get modified. However, arrays are passed call-by-reference, which means that a reference (i.e., pointer) to the array is passed rather than the array itself.
It would be extremely wasteful to copy a whole array to do call-by-value on arrays. What if the array had a million elements? Call-by-value would be very slow in that case! So arrays are passed call-by-reference, and this means that if a function modifies an array element, the element keeps that change even when the function is done.
Since arrays are passed by reference, the function can equally be declared in the two following ways:
int func(int A[]);
int func(int *A);
Both of these declarations say essentially the same thing. The first says that an array is going to be passed in by reference, and the reference to an array is simply a pointer to the first element. The second says we are going to get a pointer to an integer, and we are left to guess that the pointer will refer to an array of ints.
Other resources
http://cplus.about.com/library/weekly/aa040702a.htm About.com’s Pointer help page
http://cplus.about.com/library/weekly/aa040802a.htm About.com’s Array help page
http://cplus.about.com/library/weekly/aa042402a.htm About.com’s Memory Allocation help page