Pointers

A pointer is a variable that holds a memory address. The entire pointer model follows from this definition.

Core Syntax

* has three distinct roles depending on context — the primary source of confusion for beginners:

ExpressionContextMeaning
int *p;declarationp holds the address of an int
p = &x;address-of&x yields a pointer to x; type: int *
*p = 5;dereferencefollows the pointer and writes to the target
p + 1arithmeticmoves sizeof(int) bytes forward — not 1 byte

Pointer Arithmetic

p + n computes (char *)p + n * sizeof(*p). Subtraction yields ptrdiff_t — the signed count of elements between two pointers. Arithmetic is only well-defined within the same array object (or one-past-the-end).

Array Decay

Array expressions decay to a pointer to their first element in almost every context. The exceptions: sizeof, _Alignof, and the address-of operator &. This decay is why sizeof(arr) gives the full array size in the declaring scope but sizeof(p) gives 8 (pointer size) after assignment — and why every C API that accepts an array also takes an explicit length.

NULL

NULL is a zero-value pointer — the safe “no value” sentinel. Dereferencing it is undefined behaviour (typically SIGSEGV on real hardware, but relying on that is still UB). Always check malloc’s return value: it returns NULL on failure.

void *

void * holds any data pointer without a cast (unlike C++). Arithmetic and dereference are illegal on void * (element size unknown). Cast to unsigned char * for byte-level work; cast to the correct type before dereferencing. This is the basis of malloc, memcpy, qsort, and all C generics.

Double Pointers

To modify a pointer inside a function, pass a pointer to it (int **). The canonical use: allocating memory inside a helper and writing the result back to the caller’s pointer variable.

const on Pointers

Four combinations; read right to left:

int       *p;            /* pointer to int — both mutable        */
const int *p;            /* pointer to const int — can't write *p */
int * const p;           /* const pointer to int — can't reassign p */
const int * const p;     /* const pointer to const int           */

const does not propagate through double pointers — assigning const int ** to int ** is a compile error because it would create a back-door to strip const.

Strict Aliasing

Every pointer has a provenance — the object it was derived from. Accessing an object through a pointer of a different (non-character) type is undefined behaviour under C11 §6.5 (strict aliasing rule). The exception: unsigned char * and char * may alias any type. The safe type-punning pattern is memcpy into a correctly-typed variable.

Function Pointers

Functions live at memory addresses too. int (*fp)(int, int) declares a pointer to a function taking two ints and returning int. typedef is essential for readability. Arrays of function pointers form dispatch tables — C’s mechanism for what OOP languages do with virtual methods. See function-pointers.

Key Gotcha: void * + n is illegal

Standard C forbids void * + n because sizeof(void) is undefined. GCC extends the language to allow it (treating it like char * + n), but portable code must cast to unsigned char * first.

Appears In

m02-pointers-memory-model, m03-dynamic-memory, m04-structs-unions-bitfields