Dataslope logoDataslope

Pointers

Addresses, dereferencing, pointer types, NULL, and pass-by-pointer in C

A pointer is just a variable whose value is the address of another piece of memory. That one sentence is the whole concept — and also the source of half the things that make C feel hard.

Variables live at addresses

When you write int x = 42;, the compiler picks a memory address for x, makes sure that address holds the four bytes of 42, and from then on the name x refers to that address.

The & operator gives you the address itself. The %p format specifier prints it in hex (after a cast to void *).

Code Block
C 17 (201710L)

The addresses you see are inside the WASI sandbox's virtual address space; they are not your laptop's RAM addresses. But the idea is universal.

Declaring and dereferencing pointers

A pointer's type tells the compiler what it points to:

int    *pi;   // pi is a pointer to int
double *pd;   // pd is a pointer to double
char   *ps;   // ps is a pointer to char (often the start of a string)

Two operators move between values and addresses:

OperatorReads asReturns
&x"address of x"a pointer
*p"value at p"the thing it points to (dereference)
Code Block
C 17 (201710L)

NULL: the "points to nothing" pointer

A pointer that does not point anywhere meaningful should be NULL (defined in <stddef.h>, usually as ((void*)0)). Dereferencing a NULL pointer is undefined behavior — usually a crash.

Code Block
C 17 (201710L)

Always initialize pointers

An uninitialized pointer holds garbage — whatever bytes happened to be in that memory before. Dereferencing it is undefined behavior. Either give it a real address or set it to NULL.

Why pointers matter: pass-by-pointer

C passes arguments by value. If a function takes an int, it gets a copy. Modifying that copy does nothing to the caller.

Code Block
C 17 (201710L)

The try_to_swap function does swap its local copies, but the caller's x and y are untouched. To actually modify the caller's variables, pass their addresses:

Code Block
C 17 (201710L)

This is also how functions can "return" multiple values — give them output pointers.

Code Block
C 17 (201710L)

const pointers vs pointers to const

Two const positions, two meanings:

DeclarationCan you change the pointer?Can you change what it points to?
int *pyesyes
const int *p (pointer to const)yesno
int *const p (const pointer)noyes
const int *const pnono

Read right-to-left: "p is a const pointer to a const int."

Library convention: const pointers for read-only inputs

When a function only reads through a pointer, declare it const T *. It signals intent, lets callers pass pointers to read-only data, and helps the compiler optimize.

void *: the type-erased pointer

A void * is a pointer with no element type. It can hold the address of anything, but you cannot dereference it directly — the compiler does not know how many bytes to read or how to interpret them.

malloc returns void *. So do many generic library functions like memcpy. You assign or cast it back to a typed pointer to use it.

Code Block
C 17 (201710L)

Pointers to pointers

A pointer is a value, so you can take its address too. Patterns like this are how you write functions that allocate memory on behalf of the caller and store the new pointer in one of the caller's variables.

Code Block
C 17 (201710L)

We will see this pattern again in the heap chapter when a function needs to replace the caller's pointer with a new allocation.

Practice: swap two integers

Challenge
C 17 (201710L)
Implement swap()

Implement void swap(int *a, int *b) so that, after the call, the values pointed to by a and b are exchanged. The provided main calls swap and prints the result.

Practice: min and max via output pointers

Challenge
C 17 (201710L)
Return two values via output pointers

Implement void min_max(int a, int b, int c, int *out_min, int *out_max) so it writes the minimum of a, b, c to *out_min and the maximum to *out_max. The provided main calls it for (7, 2, 9) and prints min=2 max=9.

Test your understanding

QuestionSelect one

What does the expression *p do when p is an int *?

It computes the product of p with itself.

It takes the address of p.

It reads (or assigns to) the int value stored at the address p holds.

It marks p as a pointer in the declaration.

QuestionSelect one

Why does the function void try_to_swap(int a, int b) fail to actually swap the caller's variables?

Because C does not support swapping at all.

Because the compiler optimizes the swap away.

Because C passes arguments by value — the function gets copies of the caller's ints, and modifying the copies leaves the originals untouched.

Because int is not large enough to be swapped on this machine.

QuestionSelect one

Given const int *p, which statement is true?

You cannot change p to point at a different int.

You cannot modify the int that p currently points at through p.

The pointer is automatically initialized to NULL.

The compiler stores the int in read-only memory.

On this page