Introduction to C Pointers

By

Tagged: C

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.

Want me to talk about your product? You can sponsor this site.

Related posts about clang: