Introduction to C Pointers
By Flavio Copes
Learn what C pointers store, how to obtain and dereference an address safely, how pointers relate to arrays, and what NULL means.
Pointers are one of the most confusing/challenging parts of C, in my opinion. Especially if you are new to programming, but also if you come from a higher level programming language like Python or JavaScript.
In this post I want to introduce them in the simplest yet not-dumbed-down way possible.
A pointer is an object whose value represents the address of another object or function.
When you declare an integer number like this:
int age = 37;
we can use the & operator to get its address:
printf("%p\n", (void *)&age);
printf() expects a void * argument for the %p conversion, so the example converts &age to void *. The printed representation is implementation-defined and can look different on every run.
We can assign the address to a variable:
int *address = &age;
int *address declares a pointer to an integer. The type matters: it tells the compiler what kind of object we expect to find at that address.
We can use the unary * operator to read the object the pointer points to. This is called dereferencing the pointer:
int age = 37;
int *address = &age;
printf("%d\n", *address); /* 37 */
The * in a declaration is part of the pointer declarator. In an expression, it means “access the object this pointer points to”.
Dereferencing also lets us change that object:
int age;
int *address = &age;
*address = 37;
printf("%d\n", age); /* 37 */
Invalid pointers
A pointer must point to a valid object before you dereference it. This is wrong:
int *address;
*address = 37; /* undefined behavior */
The uninitialized pointer has an indeterminate value.
Use the null pointer constant NULL when a pointer intentionally points to no object:
#include <stddef.h>
int *address = NULL;
You can compare a pointer with NULL, but dereferencing a null pointer causes undefined behavior:
if (address != NULL) {
printf("%d\n", *address);
}
A pointer can also become invalid when the object it points to reaches the end of its lifetime. For example, you must not use a pointer to dynamically allocated memory after calling free() on that memory.
Pointers are a great opportunity because they force us to think about memory addresses and how data is organized.
Pointers and arrays
Arrays are one example. When you declare an array:
int prices[3] = { 5, 4, 3 };
prices is an array, not a pointer. In most expressions, however, the array expression is automatically converted to a pointer to its first element:
printf("%d\n", *prices); /* 5 */
We can access the second element with pointer arithmetic:
printf("%d\n", *(prices + 1)); /* 4 */
Adding 1 advances by the size of one int, not by one byte. Pointer arithmetic is defined only within an array object and one position past its end. The one-past pointer can be used in comparisons, but it must not be dereferenced.
Strings in C are usually stored as arrays of char ending with a null character, so pointers are also central to string handling.
Pointers have many more applications, including letting a function modify an object owned by its caller, working with dynamically allocated memory, and referring to functions.
Related posts about clang: