Pointers in C

Pointers are one of the most important and powerful concepts in the C language. They allow direct manipulation of the computer's memory. In this lesson, the basics of pointers in C language will be presented, from their declaration and initialization to the use of the address & and indirection * operators.

Key Takeaways
  • Pointers, also called pointer variables, store addresses rather than values inside themselves;
  • A pointer just declared is not initialized but points to a random memory location;
  • With the address operator & you can assign the address of a variable to a pointer;
  • With the indirection operator * you can access the value pointed to by the address.

Pointers

In the previous lesson we learned two fundamental concepts:

  1. Each variable is associated with an address to a memory location;
  2. The address of a variable can be obtained as an unsigned integer number.

At this point pointers come into play. In fact, it is possible in C language to take the address of a variable and assign it to a special type of variable that takes the name of pointer.

Definition

Pointer

In C language, a pointer, also called pointer variable, is a special type of variable that stores memory addresses inside itself.

In technical jargon, when a pointer p contains the address of a variable x it is said that p points to x.

For example, a pointer p that points to the address of a variable x can be represented graphically in this way:

Graphical representation of a pointer
Picture 1: Graphical representation of a pointer

Declaration of a Pointer

The declaration of a pointer or pointer variable is not very different from the declaration of a normal variable. The main difference lies in the fact that the variable name must be preceded by an asterisk.

For example, wanting to declare a pointer variable to an integer named p the syntax is the following:

int *p;

This declaration, in essence, tells the compiler that the variable p is able to point to memory areas that contain an integer. These memory areas can be variables or they can be something else as we will see in the future.

When we declare a pointer, its declaration can appear together with that of other variables. For example:

int x, *p, y;

In this case we have defined two variables of type int, x and y, and a pointer variable to int named p.

Note

Pointer Declaration Style

In many books and websites about the C language the style used to declare pointers is the following:

int* p;

In other words the asterisk is placed immediately after the type and a space is inserted between the asterisk and the variable name.

This style is used to highlight the fact that the variable p is not of integer type but of type pointer to int.

Although the purpose of such a style is noble, it could cause errors.

Those who use this style, in fact, would be tempted to write code in this way:

int* p, q;

At a careless glance it would seem to have declared two pointers to integer. In reality, we have declared a pointer to integer, p, and an integer variable q!

Our advice, to avoid errors, is to avoid using this programming style.

So, instead of writing:

/* Not Recommended */
int* p;

it is always better to write:

/* Recommended */
int *p;

The C language standard requires that a pointer variable point exclusively to objects of a specific type: the referenced type.

For example the following variable p:

int *p;

can only point to integers. While the variable q:

double *q;

can only point to double.

As for the referenced type, instead, there are no restrictions. Indeed, a pointer could also point to another pointer thus realizing pointers of pointers that we will see in the future.

Summing up:

Definition

Declaration of a Pointer in C Language

In C language the syntax to declare a pointer is the following:

type_name *pointer_name;

A pointer can only point to memory areas that contain data of the specified type type_name.

The specified type takes the name of referenced type.

The C language makes available two operators to work with pointers.

The first operator is the address operator that we have already seen in the previous lesson. Through this operator we can obtain the address of a variable in memory.

The second operator is the indirection operator that allows access to the content pointed to by an address.

These two operators are fundamental and we will now see how to use them with pointers.

Initializing a Pointer with the Address Operator

When a pointer variable is declared the compiler reserves the space necessary to contain an address. However on the just declared pointer no assumptions should be made about the content.

In other words when a pointer is declared it is not automatically initialized and could point to an invalid memory location.

/* Declaration of a pointer */
int *p;
/* At this point p points to a random location */

For this reason it is of fundamental importance to initialize a pointer just declared before using it.

The first way to initialize a pointer is to use the address operator to assign it the address of a variable. For example:

int x = 5;
int *p;

/* Initialize p with the address of x */
p = &x;

In this example, after initialization, p will point to x, that is p will contain the address of x.

Pointer of the example
Picture 2: Pointer of the example

In the figure we can observe that the variable x is located at location 2000 and contains the value 5. After initialization the pointer p will contain the value 2000 which is the address of the variable x.

We can also directly initialize a pointer when we declare it. Going back to the previous example we can write:

int x = 5;
/* Declare and initialize p */
int *p = &x;

Summing up:

Definition

Initialization of a Pointer in C Language

A pointer just declared in C language points to a random memory location.

Initializing a pointer means assigning it an address through the address operator:

type_name x;
type_name *p = &x;

Only after a pointer has been initialized it can be used.

Indirection Operator *

We have seen that we can assign an address to a pointer variable. Now we enter the heart of their use.

The power of the C language lies in the fact that when working with a pointer variable it is possible to access the content of the pointed memory location. In other words, even if the pointer variable contains an address, it is possible to read and write the value contained at the corresponding memory location.

To do this it is necessary to use the indirection operator: *.

Let's take the following example:

1
2
3
4
int x = 5;
int *p = &x;

printf("%d\n", *p);

In this piece of code at line 2 first we assign the address of x to the pointer p with the address operator.

Subsequently, in line 4 we read the content of the memory location pointed to by p and print it on screen with the printf function.

Note well that the value of x will be printed. The address will not be printed.

All this thanks to the indirection operator.

Definition

Indirection Operator: *

In C language, the indirection operator is used to access the value contained in the memory location pointed to by an address.

The syntax to use it is the following:

/* Given a pointer */
type *p;

/* After assigning an address to p */
p = &x;

/* Access the content pointed to by p */
y = *p;

The indirection operator can be seen as the inverse of the address operator. In other words we can write a line of code like this:

y = *&x;

This line is completely equivalent to:

y = x;

This derives from the fact that the indirection operator cancels the address operator.

As long as a pointer points to a variable, it represents in all respects an alias of the variable itself.

For example, given a pointer p that points to a variable x:

int *p = &x;

As long as p points to x, *p will always have the same value as x. Moreover, changing the value of *p also modifies the value of x.

To better understand let's study an example. We declare an integer variable x and assign it a value, for example 4:

int x = 4;

Subsequently, we declare a pointer to integer p:

int *p;

Initially p points to an invalid or at least random address:

The pointer is initially not initialized
Picture 3: The pointer is initially not initialized

We assign the address of x to the pointer p:

p = &x;

So now p points to x:

Now the pointer is initialized
Picture 4: Now the pointer is initialized

Let's try to print the value of x and the value pointed to by p:

printf("%d\n", x);
printf("%d\n", *p);

The result we obtain is the following:

4
4

Let's now try to modify the value pointed to by p through the indirection operator:

*p = 5;

The situation we now have is that shown in the figure:

Modification of the value through pointer
Picture 5: Modification of the value through pointer

So, if we print again the value of x and the value pointed to by p we obtain:

printf("%d\n", x);
printf("%d\n", *p);
5
5

Summing up:

Definition

Pointers as Aliases of Variables - Dereferencing

Using the indirection operator, a pointer becomes for all purposes an alias of the pointed variable.

So if we have defined:

type x;
type *p;

p = &x;

Then the following expressions are equivalent:

*p
x

The value located in the memory location pointed to by a pointer takes the name of referenced value.

In technical jargon, applying the indirection operator takes the name of dereferencing.

Note

Never apply the indirection operator to an uninitialized pointer.

We have said that an uninitialized pointer points to a random memory location.

Accessing the value pointed to by an uninitialized pointer can cause unpredictable behavior of our program.

For example:

int *p;
/* We do not initialize p */

/* We try to print the content pointed to by p */
/* The behavior is not predictable */
printf("%d\n", *p);

The behavior of the program in this case will not be known in advance. In some cases the output of the printf could be a completely random value. In the worst case our program could crash.

Another serious case is that of writing the value pointed to by an uninitialized pointer.

For example:

int *p;
*p = 7;

In this case, if the random address contained by p was valid we have written the value 7 at that address. At this point the behavior of the program is completely random since that address could belong to a control variable or similar.

If the address was not valid our program will surely crash.

In many cases the compiler could generate an error when we use uninitialized pointers.

For example, if we take the following program undefined.c:

#include <stdio.h>

int main() {
    int *p;
    printf("%d\n", *p);
    return 0;
}

And we try to compile with GCC activating the -Wall option in this way:

gcc -Wall undefined.c -o undefined

We obtain a warning of this type:

undefined.c: In function 'main':
undefined.c:5:2: warning: 'p' is used uninitialized in this function [-Wuninitialized]
    5 |  printf("%d\n", *p);
      |  ^~~~~~~~~~~~~~~~~~

Assignment of Pointers

In C language it is possible to use the assignment operator, =, to copy pointers. In other words it is possible to copy the address contained in a pointer to another. This is only possible in the case where the two pointers point to the same type.

For example, suppose we have an integer variable x:

int x = 5;

Subsequently we declare two pointers:

int *p;
int *q;

The two pointers will point to two random locations:

Initially the two pointers point to random locations
Picture 6: Initially the two pointers point to random locations

We initialize the pointer p:

p = &x;

Now p points to the variable x:

First pointer initialized
Picture 7: First pointer initialized

We can initialize q by copying p, that is by assigning it the value contained in p which is the address of x:

q = p;

At this point also q will point to x:

Second pointer initialized by copying the address from the first
Picture 8: Second pointer initialized by copying the address from the first

From this moment on we can change the value of x by modifying both *p and *q.

If we modify *p:

*p = 9;

The situation will be:

Modification of the variable value through the first pointer
Picture 9: Modification of the variable value through the first pointer

Subsequently, if we modify *q:

*q = 21;

The situation will become:

Modification of the variable value through the second pointer
Picture 10: Modification of the variable value through the second pointer

Summing up:

Definition

Assignment of Pointers

In C language it is possible to assign an address to a pointer through the assignment operator, =. In particular it is possible to assign to a pointer the address contained in another:

type *p;
type *q;

q = p;

In this case after assignment the destination pointer will contain the same address.

Attention must be paid not to confuse the assignment of the content of a pointer with the assignment of the pointed value. In fact, the following two lines of code are different:

q = p;
*q = *p;

In the first case we are assigning to q the address contained in p. In the second case we are assigning to the memory location pointed to by q the value contained in the memory location pointed to by p.

To better understand let's take an example. Suppose we have two variables:

int x = 10;
int y = 20;

Let's also suppose we have two pointers that point to them:

int *p = &x;
int *q = &y;

At this point the situation is the following:

Two pointers to different variables
Picture 11: Two pointers to different variables

If we execute the following line:

*q = *p;

We are in effect executing the following operation:

y = x;

So the result becomes:

Copy of dereferenced values
Picture 12: Copy of dereferenced values

If instead we execute the following line:

q = p;

At this point both p and q will point to x and the situation becomes:

Copy of addresses
Picture 13: Copy of addresses

In Summary

In this lesson we introduced the basics of pointers in C language. We saw how pointers are variables that contain the address of another variable instead of the value itself and how they can be declared and initialized. We also saw how the address & and indirection * operators can be used to access and modify the value of a variable through its memory address.

However, in this lesson we limited ourselves to using pointers as aliases of other variables. A use that, at first glance, can appear quite limiting in reality. Starting from the next lesson we will begin to study much more powerful and concrete uses starting from their use as function arguments.