C Pointers Cheat Sheet
Covers C pointer basics, pointer arithmetic with arrays, function pointers, malloc/free dynamic memory, and common undefined-behavior pitfalls.
Pointer Basics
Declaring, addressing, and dereferencing pointers in C.
int x = 10;int *p = &x; // p stores the address of xprintf("%d\n", *p); // dereference: prints 10*p = 20; // modifies x through pint *nullPtr = NULL; // always initialize pointersif (nullPtr != NULL) { printf("%d\n", *nullPtr);}printf("size of pointer: %zu\n", sizeof(p)); // typically 8 on 64-bit systems
Pointers & Arrays
Array names decay to pointers to their first element.
int arr[5] = {1, 2, 3, 4, 5};int *p = arr; // equivalent to &arr[0]for (int i = 0; i < 5; i++) { printf("%d ", *(p + i)); // same as arr[i]}// 2D array pointerint matrix[3][4];int (*rowPtr)[4] = matrix; // pointer to an array of 4 intsprintf("%d\n", rowPtr[1][2]); // matrix[1][2]char *str = "hello"; // pointer to string literal (read-only)char buf[] = "hello"; // mutable copy on the stack
Pointers & Functions
Pass by pointer to modify caller data, and use function pointers.
void increment(int *p) { (*p)++;}int x = 5;increment(&x); // x is now 6// function pointerint add(int a, int b) { return a + b; }int (*opPtr)(int, int) = add;printf("%d\n", opPtr(2, 3)); // 5// array of function pointersint subtract(int a, int b) { return a - b; }int (*ops[2])(int, int) = { add, subtract };printf("%d\n", ops[1](5, 3)); // 2
Dynamic Memory (malloc/free)
Manual heap allocation with the C standard library.
#include <stdlib.h>int *arr = malloc(10 * sizeof(int)); // uninitialized memoryif (arr == NULL) { // allocation failed, handle error}int *zeroed = calloc(10, sizeof(int)); // zero-initialized memoryarr = realloc(arr, 20 * sizeof(int)); // grow/shrink existing allocationfree(arr);free(zeroed);arr = NULL; // avoid dangling pointer after free
Common Pitfalls
Mistakes that cause undefined behavior with C pointers.
- Dangling Pointer- Pointer still used after the memory it points to was freed or went out of scope.
- Memory Leak- Allocated memory (malloc/calloc) never freed, becoming unreachable.
- NULL Dereference- Dereferencing a NULL or uninitialized pointer crashes the program (segfault).
- Buffer Overflow- Writing past the end of an array through pointer arithmetic corrupts adjacent memory.
- Double Free- Calling free() twice on the same pointer; undefined behavior, often heap corruption.
- Pointer vs Array Confusion- sizeof(arr) gives the full array size, but sizeof(ptr) gives only the pointer's size (e.g. 8 bytes) once it has decayed.
Pointers to Pointers
Use double indirection to let a callee reallocate or reassign a caller's pointer.
void allocate(int **out, int n) { *out = malloc(n * sizeof(int)); // modifies the caller's pointer itself}int *arr = NULL;allocate(&arr, 10); // pass address of the pointerarr[0] = 1;free(arr);// common in argv-style arrays of stringsvoid printAll(char **strings, int count) { for (int i = 0; i < count; i++) { printf("%s\n", strings[i]); // strings[i] is a char* }}int main(int argc, char **argv) { // argv is char** printAll(argv, argc);}
const Correctness with Pointers
Distinguish pointer-to-const, const-pointer, and const-pointer-to-const.
int x = 5, y = 10;const int *p1 = &x; // pointer to const int: *p1 = 1 is ERROR, p1 = &y is OKint *const p2 = &x; // const pointer to int: *p2 = 1 is OK, p2 = &y is ERRORconst int *const p3 = &x; // const pointer to const int: both are ERRORvoid readOnly(const int *p) { // caller's data is protected from modification through p printf("%d\n", *p);}// read declarations right-to-left: 'const int *const p3' =// p3 is a const pointer to a const int
void* for Generic Programming
Write type-agnostic code using untyped pointers, common in C library APIs.
void printAsInt(void *data) { int *ip = (int *)data; // must cast before dereferencing void* printf("%d\n", *ip);}// generic swap using void* and a byte-wise copyvoid swap(void *a, void *b, size_t size) { unsigned char temp[size]; memcpy(temp, a, size); memcpy(a, b, size); memcpy(b, temp, size);}int x = 1, y = 2;swap(&x, &y, sizeof(int));// void* arithmetic is a GCC extension, not standard C -// cast to char* first for portable byte-wise pointer mathchar *bytePtr = (char *)someVoidPtr + offset;
Callback Pattern with qsort
Function pointers let library code call back into user-supplied comparison logic.
#include <stdlib.h>int compareInts(const void *a, const void *b) { int ia = *(const int *)a; int ib = *(const int *)b; return (ia > ib) - (ia < ib); // avoids overflow vs. ia - ib}int arr[] = {5, 2, 8, 1, 9};qsort(arr, 5, sizeof(int), compareInts);// bsearch uses the same callback signatureint key = 8;int *found = bsearch(&key, arr, 5, sizeof(int), compareInts);
Advanced Pointer Semantics
Subtler rules that matter once the basics are second nature.
- Strict aliasing rule- Accessing an object through a pointer of an incompatible type (except char*) is undefined behavior; the compiler may reorder around it.
- restrict keyword (C99)- Tells the compiler a pointer is the only reference to its memory, enabling more aggressive optimization (e.g. memcpy vs memmove).
- Pointer arithmetic bounds- Computing a pointer more than one-past-the-end of an array is undefined behavior, even if never dereferenced.
- Pointer comparison- Comparing pointers with < or > is only well-defined when both point into the same array or object.
- Flexible array members- struct Foo { int len; char data[]; } lets one allocation hold a variable-length trailing array without a separate pointer indirection.
- Opaque pointer idiom- Exposing only a forward-declared struct pointer (typedef struct Handle *Handle_t) in a header hides implementation details from callers.
Always set a pointer to NULL immediately after free() - a freed-but-non-NULL pointer is a dangling pointer, and dereferencing or freeing it again is undefined behavior that NULL protects against, catching a lot of accidental double frees.