An Introduction to C Pointers

Pointers are special type of variable in C. The primary purpose of a pointer is to store, or “point to”, a memory address. In a language like C, where the programmer must directly work with the system’s memory, having a special container to store memory addresses for you makes life much easier. This post aims to explain the syntax and usage of pointers in an easy to understand way. A basic understanding of C and general programming concepts such as data types, variables, memory addresses, and arrays is expected.

Pointer Basics

A variable is a pointer when an asterisk (*) precedes the variable name. Pointers can be of any C-type variable. Below is an example declaration of a pointer of type integer.

int *ptr;

The pointer above can store any memory address of an int-type variable. Like normal variables, pointers are only capable of storing addresses from variables of their own type. For instance, a char-type pointer cannot store the address of a float-type variable, and vice-versa. Prefix a variable with an ampersand (&) character to get its address.

&var

With this information we can now store the address of a variable into a pointer. Consider the following code:

int a = 5;	//initialize a's value to 5
int *ptr = &a;	//store a’s address (&a) into ptr

The code above stores the value 5 into a’s address. Next we store a’s address into ptr. Be careful not to confuse a’s address, and a’s value here. The integer 5 is the value, but the memory address is whatever the compiler stored a in.

Here is what I got when I printed a’s address with my compiler.

//print a's address
printf("&a = %x", &a);

output:

&a = bcfa04

We see that a’s address is just a seemingly random hexadecimal number. Remember that a pointer is just a variable that stores a memory address. Therefore, if we print our pointer ptr, we should get the same result as above, a’s address.

//print the address stored in the pointer: ptr
printf("ptr = %x", ptr);

output:

ptr = bcfa04

There we have it, the same result as above. Here I’ll explain the true power of pointers, retrieving the value from inside a memory address. Excluding pointer initialization, anytime you prefix a pointer with the * operand, you will get the value stored within the memory address that the pointer points to. Let’s prove this with code.

//access the value stored in a's address
//by prefixing the pointer with the * operand, we can access the value stored in an address
printf("*ptr = %d", *ptr);

output:

*ptr = 5

This can be a bit confusing. We established that a pointer’s purpose is to store a memory address, but now we see it can also retrieve the value from that address. This is called dereferencing the pointer. Try to think of it this way. A pointer by itself is only a map with directions (an address) to a chest. The chest at this address holds a treasure (a value). With the map alone, we can do little more than travel to where the treasure chest is, but since the treasure is locked within the chest we cannot retrieve it. However, by using the * operand, we turn our pointer map into a key. This key unlocks our chest and reveals the spoils!

Remember, a pointer without the asterisk is only a map to an address.
ptr ← I’m just a map!

A pointer with the asterisk before it is a key that reveals the hidden treasure in that address.
*ptr ← I’m the key to treasure!

Pointer Array

It is possible to create an array of pointers that store multiple memory addresses. The syntax and rules for pointer arrays are basically the same as normal C arrays.

int *ptr_arr[3];

Above is a declaration of the pointer array ptr_arr. The pointer array can store up to three addresses holding integer-type values. The array hasn’t been initialized, so there is essentially nothing stored in the array yet. Let’s dive into the code.

int  array[] = { 100, 200, 300 }; //array of int-type values
int i = 0; //array index
int *ptr_arr[3]; //array of int-type pointers

for (i = 0; i < 3; i++) 
{
    //assign the address of array[i] to ptr_arr[i]
    ptr_arr[i] = &array[i];

    // the value of array[i], accessed by *ptr_arr[i]
    printf("Value of array[%d] = %d\n", i, *ptr_arr[i]);
}

Let’s go through the code step-by-step. First, we initialize an array of int-type values, initialize the index for our for-loop, and declare an array of pointers. For each element in the array, we store the address of array[i] into ptr_arr[i]. Finally, we print the value stored in that address by accessing it with our pointer array. Here is the result:

output:

Value of array[0] = 100
Value of array[1] = 200
Value of array[2] = 300

Pointer Arithmetic

In the previous section, I described how pointers can be used to store an array of memory addresses. We stepped through each value in the array in the traditional manner by referencing its element with an index. There is another way to access each address in a pointer array—by using pointer arithmetic!

The slot in the memory stack being pointed to can be incremented or decremented just like integer data types. The syntax is exactly as you would imagine it to be:
ptr++; ← increment the pointer to reference the next address in the memory stack

Here is some example code:

int array[] = { 10, 20, 30 }; //array of values
int i = 0; //array index
int *ptr = array; //put the first address of ‘array’ in the pointer ‘ptr’

//loop through all elements of ‘array’
for (i = 0; i < MAX; i++)
 {
    //print the address of the array's current element
    printf("Address of array[%d] = %x\n", i, ptr);
    
    //print the value of the array's current element
    printf("Value of array[%d] = %d\n\n", i, *ptr);
    
    //Moves to the next memory location of ‘array’.
    //If array is an integer and the first memory address is 1000,
    //then the second memory address will be 1004. It points to the next
    //integer location which is the next 4-bytes from the current location.
    ptr++;
}
First, we initialize an array of ints, an indexing variable, and a pointer.
The pointer points to the memory address of the first element in array (array[0]).
Inside the for-loop, we are doing three things:
  1. Print the memory address of the element in the array we are currently indexing. Notice, ptr has no asterisk before it. This is because we are not dereferencing it yet. We just want the address itself.
  2. Print the value stored inside the memory address that ptr is pointing to. Notice that there is an asterisk prefixing ptr. We are dereferencing ptr to get its value.
  3. Lastly, here is where we actually perform the pointer arithmetic. We simply use the increment operator to reference the memory address immediately after the one we are currently referencing. So, if ptr is currently pointing to 1000, after it is incremented it will point to 1004.

Here is the code output:

Address of array[0] = 1000
Value of array[0] = 10

Address of array[1] = 1004
Value of array[1] = 20

Address of array[2] = 1008
Value of array[2] = 30

Arrow Operator

Lastly, I’ll discuss the arrow operator. The arrow operator is actually very simple if you understand the concept of dereferencing.

Lets say we have a simple struct:

struct Data
{
    int num;
    bool is_valid;
};

Up until this point, we’ve only dealt with common C variables, but not a data structure. A data structure is a class-like structure with multiple member variables in it. A structure variable that is not a pointer can access its variables with the dot operator, like so:

Data var;
var.num = 5;

Simple. However, when we are dealing with a pointer to a struct, the dot operator will not work. This is where the arrow operator comes in.

Arrow operators dereference the variable of a struct.

Lets make a pointer variable of type Data:

Data* ptr;

If we want to change the value of the num variable, we must use the arrow operator!

ptr->num = 5;

That’s it! Technically, there is a way to use the dot operator to dereference a member variable of a struct. It can be accomplished as below:

(*ptr).num = 5;

Can you understand why this works? By prefixing an * to ptr (and surrounding it in parentheses), we are dereferencing the pointer. Once the pointer is dereferenced, we can access the member variable as normal (with the dot operator).

Conclusion

Pointers are probably the most difficult facet of the C language to fully grasp. They are seemingly intuitive, but actually take quite a bit of time to get used to. Really the best way to master them is to use them regularly when you program. If you get stuck on the basics, read this tutorial to get back on track!

[go to home page]