C Pointers and Memory Allocation


Other resources

About.com's Pointer help page

About.com's Array help page

About.com's Memory Allocation help 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 no more).

A pointer is 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.

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.

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.

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 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.

Memory Allocation and Deallocation

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.

Pointers and arrays

Allocating individual integers isn't all that useful either. But malloc() can also allocate arrays. We will discuss the similarity of pointers and arrays in class, and the textbook discusses this in section 3.13. But essentially, a pointer can be used as an array, and you can index it just like an array, as long as it is pointing to enough memory. 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 and arrays

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,

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.

If you ...