C Pointers

This chapter introduces you to pointers, an important part of the C language. Pointers provide a powerful and flexible method of manipulating data in your programs. Today you will learn

  • The definition of a pointer
  • The uses of pointers
  • How to declare and initialize pointers
  • How to use pointers with simple variables and arrays
  • How to use pointers to pass arrays to functions

As you read through this chapter, the advantages of using pointers might not be clear immediately. The advantages fall into two categories: things that can be done better with pointers than without, and things that can be done only with pointers. The specifics should become clear as you read this and subsequent chapters. At present, just know that you must understand pointers if you want to be a proficient C programmer.

What Is a Pointer?

To understand pointers, you need a basic knowledge of how your computer stores information in memory. The following is a somewhat simplified account of PC memory storage.

Pointers and Simple Variables

In the example just given, a pointer variable pointed to a simple (that is, non array) variable. This section shows you how to create and use pointers to simple variables.

Declaring Pointers

A pointer is a numeric variable and, like all variables must be declared before it can be used. Pointer variable names follow the same rules as other variables and must be unique. This chapter uses the convention that a pointer to the variable name is called p_name. This isn't necessary, however; you can name pointers anything you want (within C's naming rules).

A pointer declaration takes the following form:

typename *ptrname;

typename is any of C's variable types and indicates the type of the variable that the pointer points to. The asterisk (*) is the indirection operator, and it indicates that ptrname is a pointer to type typename and not a variable of type typename. Pointers can be declared along with nonpointer variables. Here are some more examples:

char *ch1, *ch2;         
/* ch1 and ch2 both are pointers to type char */

float *value, percent;   
/* value is a pointer to type float, and
/* percent is an ordinary float variable */

NOTE: The * symbol is used as both the indirection operator and the multiplication operator. Don't worry about the compiler's becoming confused. The context in which * is used always provides enough information so that the compiler can figure out whether you mean indirection or multiplication.

Initializing Pointers

Now that you've declared a pointer, what can you do with it? You can't do anything with it until you make it point to something. Like regular variables, uninitialized pointers can be used, but the results are unpredictable and potentially disastrous. Until a pointer holds the address of a variable, it isn't useful. The address doesn't get stored in the pointer by magic; your program must put it there by using the address-of operator, the ampersand (&). When placed before the name of a variable, the address-of operator returns the address of the variable. Therefore, you initialize a pointer with a statement of the form

pointer = &variable;

The program statement to initialize the variable p_rate to point at the variable rate would be

p_rate = &rate;     /* assign the address of rate to p_rate */

Before the initialization, p_rate didn't point to anything in particular. After the initialization, p_rate is a pointer to rate.

Using Pointers

Now that you know how to declare and initialize pointers, you're probably wondering how to use them. The indirection operator (*) comes into play again. When the * precedes the name of a pointer, it refers to the variable pointed to.

Let's continue with the previous example, in which the pointer p_rate has been initialized to point to the variable rate. If you write *p_rate, it refers to the variable rate. If you want to print the value of rate (which is 100 in the example), you could write

printf("%d", rate);
or this:
printf("%d", *p_rate);

In C, these two statements are equivalent. Accessing the contents of a variable by using the variable name is called direct access. Accessing the contents of a variable by using a pointer to the variable is called indirect access or indirection.Pause a minute and think about this material. Pointers are an integral part of the C language, and it's essential that you understand them. Pointers have confused many people, so don't worry if you're feeling a bit puzzled. If you need to review, that's fine. Maybe the following summary can help.

If you have a pointer named ptr that has been initialized to point to the variable var, the following are true:

  • *ptr and var both refer to the contents of var (that is, whatever value the program has stored there).
  • ptr and &var refer to the address of var.

As you can see, a pointer name without the indirection operator accesses the pointer value itself, which is, of course, the address of the variable pointed to.

These example demonstrates basic pointer use. You should enter, compile, and run this program.

Example : Basic pointer use

/* Demonstrates basic pointer use. */
#include <stdio.h>
/* Declare and initialize an int variable */

int var = 1;

/* Declare a pointer to int */

int *ptr;

main()
{
    /* Initialize ptr to point to var */

    ptr = &var;

    /* Access var directly and indirectly */

    printf("\nDirect access, var = %d", var);
    printf("\nIndirect access, var = %d", *ptr);

    /* Display the address of var two ways */

    printf("\n\nThe address of var = %d", &var);
    printf("\nThe address of var = %d\n", ptr);

    return 0;
}

Direct access, var = 1
Indirect access, var = 1
The address of var = 4264228
The address of var = 4264228

The address reported for var might not be 4264228 on your system.


ANALYSIS:

In this listing, two variables are declared. In line 7, var is declared as an int and initialized to 1. In line 11, a pointer to a variable of type int is declared and named ptr. In line 17, the pointer ptr is assigned the address of var using the address-of operator (&). The rest of the program prints the values of these two variables to the screen. Line 21 prints the value of var, whereas line 22 prints the value stored in the location pointed to by ptr. In this program, this value is 1. Line 26 prints the address of var using the address-of operator. This is the same value printed by line 27 using the pointer variable, ptr.

This listing is good to study. It shows the relationship between a variable, its address, a pointer, and the dereferencing of a pointer.

Pointers and Variable Types

The previous discussion ignores the fact that different variable types occupy different amounts of memory. For the more common PC operating systems, an int takes two bytes, a float takes four bytes, and so on. Each individual byte of memory has its own address, so a multi byte variable actually occupies several addresses.

How, then, do pointers handle the addresses of multibyte variables? Here's how it works: The address of a variable is actually the address of the first (lowest) byte it occupies. This can be illustrated with an example that declares and initializes three variables:

int vint = 12252;
char vchar = 90;
float vfloat = 1200.156004;

Now let's declare and initialize pointers to these three variables:
int *p_vint;
char *p_vchar;
float *p_vfloat;

/* additional code goes here */
p_vint = &vint;
p_vchar = &vchar;
p_vfloat = &vfloat;

Each pointer is equal to the address of the first byte of the pointed-to variable. Thus, p_vint equals 1000, p_vchar equals 1003, and p_vfloat equals 1006. Remember, however, that each pointer was declared to point to a certain type of variable. The compiler knows that a pointer to type int points to the first of two bytes, a pointer to type float points to the first of four bytes, and so on.

Pointers and Arrays

Pointers can be useful when you're working with simple variables, but they are more helpful with arrays. There is a special relationship between pointers and arrays in C. In fact, when you use the array subscript notation that you learned in previous chapters, "Using Numeric Arrays," you're really using pointers without knowing it. The following sections explain how this works.

The Array Name as a Pointer

An array name without brackets is a pointer to the array's first element. Thus, if you've declared an array data[], data is the address of the first array element.

"Wait a minute," you might be saying. "Don't you need the address-of operator to get an address?" Yes. You can also use the expression &data[0] to obtain the address of the array's first element. In C, the relationship (data == &data[0]) is true.

You've seen that the name of an array is a pointer to the array. Remember that this is a pointer constant; it can't be changed and remains fixed for the duration of program execution. This makes sense: If you changed its value, it would point elsewhere and not to the array (which remains at a fixed location in memory).

You can, however, declare a pointer variable and initialize it to point to the array. For example, the following code initializes the pointer variable p_array with the address of the first element of array[]:

int array[100], *p_array;

/* additional code goes here */
p_array = array;

Because p_array is a pointer variable, it can be modified to point elsewhere. Unlike an array, p_array isn't locked into pointing at the first element of array[]. For example, it could be pointed at other elements of array[]. How would you do this? First, you need to look at how array elements are stored in memory.

Array Element Storage

As you might remember from Day 8, the elements of an array are stored in sequential memory locations with the first element in the lowest address. Subsequent array elements (those with an index greater than 0) are stored in higher addresses. How much higher depends on the array's data type (char, int, float, and so forth).

Take an array of type int. As you learned on Day 3, "Storing Data: Variables and Constants," a single int variable can occupy two bytes of memory. Each array element is therefore located two bytes above the preceding element, and the address of each array element is two higher than the address of the preceding element. A type float, on the other hand, can occupy four bytes. In an array of type float, each array element is located four bytes above the preceding element, and the address of each array element is four higher than the address of the preceding element.

1: x == 1000
2: &x[0] == 1000
3: &x[1] = 1002
4: expenses == 1250
5: &expenses[0] == 1250
6: &expenses[1] == 1254

x without the array brackets is the address of the first element (x[0]). You can also see that x[0] is at the address of 1000. Line 2 shows this too. It can be read like this: "The address of the first element of the array x is equal to 1000." Line 3 shows that the address of the second element (subscripted as 1 in an array) is 1002. Lines 4, 5, and 6 are virtually identical to 1, 2, and 3, respectively. They vary in the difference between the addresses of the two array elements. In the type int array x, the difference is two bytes, and in the type float array, expenses, the difference is four bytes.

How do you access these successive array elements using a pointer? You can see from these examples that a pointer must be increased by 2 to access successive elements of a type int array, and by 4 to access successive elements of a type float array. You can generalize and say that to access successive elements of an array of a particular data type, a pointer must be increased by sizeof (datatype).

Example: Displaying the addresses of successive array elements

1:  /* Demonstrates the relationship between addresses and */
2:  /* elements of arrays of different data types. */
3:
4:  #include <stdio.h>
5:
6:  /* Declare three arrays and a counter variable. */
7:
8:  int i[10], x;
9:  float f[10];
10: double d[10];
11:
12: main()
13: {
14:     /* Print the table heading */
15:
16:     printf("\t\tInteger\t\tFloat\t\tDouble");
17:
18:     printf("\n================================");
19:     printf("======================");
20:
21:     /* Print the addresses of each array element. */
22:
23:     for (x = 0; x < 10; x++)
24:         printf("\nElement %d:\t%ld\t\t%ld\t\t%ld", x, &i[x],
25:             &f[x], &d[x]);
26:
27:     printf("\n================================");
28:     printf("======================\n");
29:
30:     return 0;
31: }

               Integer     Float     Double
==============================================
Element 0:     1392        1414      1454
Element 1:     1394        1418      1462
Element 2:     1396        1422      1470
Element 3:     1398        1426      1478
Element 4:     1400        1430      1486
Element 5:     1402        1434      1494
Element 6:     1404        1438      1502
Element 7:     1406        1442      1510
Element 8:     1408        1446      1518
Element 9:     1410        1450      1526
==============================================

ANALYSIS:

The exact addresses that your system displays might be different from these, but the relationships are the same. In this output, there are two bytes between int elements, four bytes between float elements, and eight bytes between double elements. (Note: Some machines use different sizes for variable types. If your machine differs, the preceding output might have different-size gaps; however, they will be consistent gaps.)

This listing takes advantage of the escape characters discussed on Day 7, "Fundamentals of Input and Output." The printf() calls in lines 16 and 24 use the tab escape character (\t) to help format the table by aligning the columns.

Looking more closely at Example, you can see that three arrays are created in lines 8, 9, and 10. Line 8 declares array i of type int, line 9 declares array f of type float, and line 10 declares array d of type double. Line 16 prints the column headers for the table that will be displayed. Lines 18 and 19, along with lines 27 and 28, print dashed lines across the top and bottom of the table data. This is a nice touch for a report. Lines 23, 24, and 25 are for loop that prints each of the table's rows. The number of the element x is printed first. This is followed by the address of the element in each of the three arrays.

Pointer Arithmetic

You have a pointer to the first array element; the pointer must increment by an amount equal to the size of the data type stored in the array. How do you access array elements using pointer notation? You use pointer arithmetic.

"Just what I don't need," you might be thinking, "another kind of arithmetic to learn!" Don't worry. Pointer arithmetic is simple, and it makes using pointers in your programs much easier. You have to be concerned with only two pointer operations: incrementing and decrementing.

Incrementing Pointers

When you increment a pointer, you are increasing its value. For example, when you increment a pointer by 1, pointer arithmetic automatically increases the pointer's value so that it points to the next array element. In other words, C knows the data type that the pointer points to (from the pointer declaration) and increases the address stored in the pointer by the size of the data type.

Suppose that ptr_to_int is a pointer variable to some element of an int array. If you execute the statement

ptr_to_int++;

the value of ptr_to_int is increased by the size of type int (usually 2 bytes), and ptr_to_int now points to the next array element. Likewise, if ptr_to_float points to an element of a type float array, the statement

ptr_to_float++;

increases the value of ptr_to_float by the size of type float (usually 4 bytes).

The same holds true for increments greater than 1. If you add the value n to a pointer, C increments the pointer by n array elements of the associated data type. Therefore,

ptr_to_int += 4;

increases the value stored in ptr_to_int by 8 (assuming that an integer is 2 bytes), so it points four array elements ahead. Likewise,

ptr_to_float += 10;

increases the value stored in ptr_to_float by 40 (assuming that a float is 4 bytes), so it points 10 array elements ahead.

Decrementing Pointers

The same concepts that apply to incrementing pointers hold true for decrementing pointers.Decrementing a pointer is actually a special case of incrementing by adding a negative value. If you decrement a pointer with the -- or -= operators, pointer arithmetic automatically adjusts for the size of the array elements.

Example: Using pointer arithmetic and pointer notation to access array elements.

1:  /* Demonstrates using pointer arithmetic to access */
2:  /* array elements with pointer notation. */
3:
4:  #include <stdio.h>
5:  #define MAX 10
6:
7:  /* Declare and initialize an integer array. */
8:
9:  int i_array[MAX] = { 0,1,2,3,4,5,6,7,8,9 };
10:
11: /* Declare a pointer to int and an int variable. */
12:
13: int *i_ptr, count;
14:
15: /* Declare and initialize a float array. */
16:
17: float f_array[MAX] = { .0, .1, .2, .3, .4, .5, .6, .7, .8, .9 };
18:
19: /* Declare a pointer to float. */
20:
21: float *f_ptr;
22:
23: main()
24: {
25:     /* Initialize the pointers. */
26:
27:     i_ptr = i_array;
28:     f_ptr = f_array;
29:
30:     /* Print the array elements. */
31:
32:     for (count = 0; count < MAX; count++)
33:         printf("%d\t%f\n", *i_ptr++, *f_ptr++);
34:
35:     return 0;
36: }

0       0.000000
1       0.100000
2       0.200000
3       0.300000
4       0.400000
5       0.500000
6       0.600000
7       0.700000
8       0.800000
9       0.900000

ANALYSIS

In this program, a defined constant named MAX is set to 10 in line 5; it is used throughout the listing. In line 9, MAX is used to set the number of elements in an array of ints named i_array. The elements in this array are initialized at the same time that the array is declared. Line 13 declares two additional int variables. The first is a pointer named i_ptr. You know this is a pointer because an indirection operator (*) is used. The other variable is a simple type int variable named count. In line 17, a second array is defined and initialized. This array is of type float, contains MAX values, and is initialized with float values. Line 21 declares a pointer to a float named f_ptr.

The main() function is on lines 23 through 36. The program assigns the beginning address of the two arrays to the pointers of their respective types in lines 27 and 28. Remember, an array name without a subscript is the same as the address of the array's beginning. A for the statement in lines 32 and 33 uses the int variable count to count from 0 to the value of MAX. For each count, line 33 dereferences the two pointers and prints their values in a printf() function call. The increment operator then increments each of the pointers so that each points to the next element in the array before continuing with the next iteration of the for the loop.

You might be thinking that this program could just as well have used array subscript notation and dispensed with pointers altogether. This is true, and in simple programming tasks like this, the use of pointer notation doesn't offer any major advantages. As you start to write more complex programs, however, you should find the use of pointers advantageous.

Remember that you can't perform incrementing and decrementing operations on pointer constants. (An array name without brackets is a pointer constant.) Also remember that when you're manipulating pointers to array elements, the C compiler doesn't keep track of the start and finish of the array. If you're not careful, you can increment or decrement the pointer so that it points somewhere in memory before or after the array. Something is stored there, but it isn't an array element. You should keep track of pointers and where they're pointing.

Other Pointer Manipulations

The only other pointer arithmetic operation is called differencing, which refers to subtracting two pointers. If you have two pointers to different elements of the same array, you can subtract them and find out how far apart they are. Again, pointer arithmetic automatically scales the answer so that it refers to array elements. Thus, if ptr1 and ptr2 point to elements of an array (of any type), the following expression tells you how far apart the elements are:

ptr1 - ptr2

Pointer comparisons are valid only between pointers that point to the same array. Under these circumstances, the relational operators ==, !=, >, <, >=, and <= work properly. Lower array elements (that is, those having a lower subscript) always have a lower address than higher array elements. Thus, if ptr1 and ptr2 point to elements of the same array, the comparison

ptr1 < ptr2

is true if ptr1 points to an earlier member of the array than ptr2 does.

This covers all allowed pointer operations. Many arithmetic operations that can be performed with regular variables, such as multiplication and division, don't make sense with pointers. The C compiler doesn't allow them. For example, if ptr is a pointer, the statement

ptr *= 2;

generates an error message. As Table indicates, you can do a total of six operations with a pointer, all of which have been covered in this chapter.

Table: Pointer operations

Operation

Description

Assignment

You can assign a value to a pointer. The value should be an address, obtained with the address-of operator (&) or from a pointer constant (array name).

Indirection

The indirection operator (*) gives the value stored in the pointed-to location.

Address of

You can use the address-of operator to find the address of a pointer, so you can have pointers to pointers. This is an advanced topic and is covered on Day 15, "Pointers: Beyond the Basics."

Incrementing

You can add an integer to a pointer in order to point to a different memory location.

Decrementing

You can subtract an integer from a pointer in order to point to a different memory location.

Differencing

You can subtract an integer from a pointer in order to point to a different memory location.

Comparison

Valid only with two pointers that point to the same array.

C Pointer Example Programs