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:
| Expression | Context | Meaning |
|---|---|---|
int *p; | declaration | p holds the address of an int |
p = &x; | address-of | &x yields a pointer to x; type: int * |
*p = 5; | dereference | follows the pointer and writes to the target |
p + 1 | arithmetic | moves 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