Skip to main content Skip to navigation

2.8 Pointers

Pointers are a very useful device in C. They are used in a wide variety of instances, such as passing information to functions, dealing with arrays, and also managing memory requirements. This is just a quickstart guide to intoduce you to pointers, so you can recognise them, and use them in a few simple applications. This section is not intended to be a comprehensive resource - you should read and practice more to appreciate pointers fully.

1. Introduction

A pointer is an object that "points" to a memory address of another object (say, a variable). It's like the index of a book - you go to the index, and look up, say, "cricket". The index will tell you on which page (e.g., like a memory address) you can find a mention of cricket. Unlike a variable or an array element that just has one piece of information associated with it (the value of that variable or element), a pointer has several pieces of information assoicated with it. Obviously, one of these pieces of information is the memory address. However, you can also use a pointer to access the value of the item kept at that memory address.

Pointers must be declared (since they are variables that store memory addresses), and the declaration format is very similar to what you have already seen, except that an asterisk (*) must be placed before the name of the pointer, e.g.

int *tag;

this has declared an integer pointer called tag. Note that if you want to declare several pointers on one line, each pointer name must have a star.

Example:

Below shows two pointers declared on one line:
int *tag, *label;
Below shows one pointer and one regular integer variable declared on one line:
int *tag, result;

The memory address of any variable can always be recovered by using the ampersand operator preceding the variable name, e.g., given a variable called total, the memory address of total is given by &total. Consider the following code fragment:

int result;
int *tag;

result = 6.5;
tag = &result;

here, the memory address of a variable result has been mapped to a pointer called tag. This is summarised in the schematic below:

On the other hand, you can obtain the content of a memory address given by a pointer by using the asterisk operator preceding the name of the pointer. Taking our example, *tag would yield the value 6.5.

It is also possible to perform arithmetic operations on pointers, but beware that the "unit" in pointer arithmetic is the size (in bytes) of the object at which the pointer points. For example, if tag is a pointer to a variable result of type float, then the expression tag + 1 refers not to the next bit or byte in memory but to the location 4 bytes away on (on 32-bit mahcines); if result were of type double, then tag + 1 would refer to a location 8 bytes (the size of a double) away, and so on.

Back to top

2. Arrays revisited

C treats the name of the array as if it were a pointer to the first element. So, if a is an array, *a is the same thing as a[0], *(a+1) is the same thing as a[1], and so on, as summarised in the schematic below:


Back to top

3. Functions revisited

You should already know that the functions you have encountered so far pass by value, where the arguuments passed to a function are left unchanged by the function call. This means that, e.g., you can't use this approach to write a function to swap the values of two variables that are passed to the function. However, by using pointers, you can get around this by passing the address of the variables - this is known as passing by reference.

Putting it into Practice:

Code up the program below to see how passing pointers as arguments to functions.
#include<stdio.h>	

void exchange ( int *a, int *b );

int main()
{
  int a=5;
  int b=7;
  
  printf("In main, variables a=%d and b=%d \n", a, b);
  exchange(&a, &b);
  printf("Back in main, variables a=%d and b=%d \n", a, b);
  return 0;
} void exchange (int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; printf("From function exchange a=%d and b=%d", *a, *b); }
Compile and run this code. Try writing some variations on this code.

Back to top

4. Dynamic memory allocation

Pointers give us a means of allocating memory for a program at run time rather than at compile time - this is called dynamic memory allocation. There are several c commands associated with dynamic memory allocation.

You have already seen the function called sizeof when using fgets to grab lines of data inputted from the keyboard (see Section 2.3). sizeof is usually used in conjunction with other commands when dynamically allocating memory.

malloc (stands for memory allocation) is a standard library function commonly used to allocate memory on the fly. The prototype for this function is:

void *malloc(size_t nbytes);

In this declaration, void * is used to indicate that malloc returns a generic pointer (ie a pointer that can point to any type of object). The actual call to function malloc takes one argument - the number of bytes to allocate (nbytes in the prototype above).

Example:

To declare memory at run time for a string that is 100 characters long, we would need to use the following code fragment:
int main()
{ /* declare a pointer to a string */ char *string_ptr; string_ptr=malloc(100);
return 0;
}

Since you'll never be running your code on a machine with an infinite amount of memory, there's always the chance that you could run out of memory while trying to dynamically allocate, so it's a good idea to always check that your malloc has actually worked. When malloc runs out of room, it returns a null pointer. To check for this condition, you need to add a conditional statement after each malloc call, that will exit from the code gracefully if you have indeed run out of memory.

Example:

Following on from the example above, we could add a check to make sure that we didn't run out of memory when allocating for string_ptr:
int main()
{ /* declare a pointer to a string */ char *string_ptr; string_ptr=malloc(100); if (string_ptr == NULL) { fprintf(stderr, "Out of memory\n"); exit(8); }
return 0;
}

However, it's more usual to use sizeof as an argument to malloc. Since each character takes 1 byte of memory, it's fine to request 100 bytes for a string 100 characters long (as in the example above). However, for other types (especially structures) this is not so straightforward - so using sizeof, which returns the size of a data type is very useful. For example, to allocate memory for an array of 5 integers, we could write:

int *ip;

ip = malloc(5 * sizeof(int));
if (ip == NULL) {
  fprintf(stderr, "Out of memory\n");
  exit(8);
}

There are related functions such as calloc and realloc. Take a look in any book on C to find out more. Finally, once you've allocated memory, it's usually a good idea to free that memory up when you're done. This can be achieved using the free function. free takes a single argument - namely the pointer associated with the memory you wish to free. It's a good idea to follow up a free statement with statement that sets the relevant pointer to NULL.

Example:

Following on from the example above, we could free up out memory allocating using string_ptr:
int main()
{ /* declare a pointer to a string */ char *string_ptr; string_ptr=malloc(100 * sizeof(char)); if (string_ptr == NULL) { fprintf(stderr, "Out of memory\n"); exit(8); } free(string_ptr); string_ptr=NULL;
return 0;
}

Back to top