Skip to Content

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