C Cheatsheet
Pointers
Use this C reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Pointer Basics
A pointer stores the memory address of another object. The type determines how dereferencing interprets the bytes.
int x = 42; int *p = &x; // & = "address of"; p holds the address of x int val = *p; // * = dereference; val = 42 *p = 100; // x is now 100 printf("address: %p, value: %d\n", (void *)p, *p); // Pointer declarations int *ip; // pointer to int char *cp; // pointer to char double *dp; // pointer to double void *vp; // generic pointer — no dereference without cast
int* p;andint *p;are identical. Butint* a, b;declaresaasint*andbasint— the*binds to the variable name.
Null Pointer
int *p = NULL; // null pointer: points to nothing p = 0; // equivalent if (p == NULL) { } // always check before dereferencing if (!p) { } // same check // *p when p == NULL is undefined behavior (usually a segfault)
Pointer Arithmetic
Arithmetic on pointers scales by the size of the pointed-to type.
int arr[] = {10, 20, 30, 40, 50}; int *p = arr; // p points to arr[0] p + 1; // address of arr[1] (not +1 byte, +sizeof(int) bytes) *(p + 2); // arr[2] = 30 p++; // p now points to arr[1] // Subscript and pointer arithmetic are equivalent arr[i] == *(arr + i) // true by definition // Pointer difference int *a = &arr[1]; int *b = &arr[4]; ptrdiff_t diff = b - a; // 3 (elements, not bytes); type: ptrdiff_t
Only arithmetic within the same array (or one past the end) is defined. Pointer arithmetic on unrelated objects is UB.
Pointers to Pointers
int x = 5; int *p = &x; int **pp = &p; // pointer to pointer to int **pp = 10; // x is now 10 *pp = NULL; // p is now NULL // Common use: output parameters for pointer values void alloc_buf(char **out, size_t n) { *out = malloc(n); } char *buf; alloc_buf(&buf, 256);
const and Pointers
| Declaration | What's const? |
|---|---|
const int *p | The int it points to (cannot modify *p) |
int * const p | The pointer itself (cannot change p) |
const int * const p | Both the pointer and the pointed-to value |
int x = 1, y = 2; const int *p = &x; // cannot do *p = 5; but can do p = &y; int * const q = &x; // can do *q = 5; but cannot do q = &y; const int * const r = &x; // neither *r = ... nor r = ... allowed // Pass-by-const-pointer: function cannot modify the object void print_str(const char *s);
Void Pointers
void * is a generic pointer — compatible with any data pointer type.
void *vp; int x = 42; vp = &x; // no cast needed for assignment int *ip = (int *)vp; // cast required to dereference or do arithmetic // malloc returns void * int *arr = malloc(10 * sizeof(int)); // implicit conversion in C (explicit in C++) // memcpy works via void * memcpy(dst, src, n);
Pointers and Arrays
int arr[5] = {1, 2, 3, 4, 5}; int *p = arr; // arr decays to &arr[0] // arr and p are interchangeable for element access arr[2] == *(arr + 2) == p[2] == *(p + 2); // all true // sizeof distinction sizeof(arr) // 20 (full array size) sizeof(p) // 8 (pointer size on 64-bit) // Passing an array to a function passes a pointer — size info lost void f(int *a, int len);
Pointer to Function
int add(int a, int b) { return a + b; } int (*fp)(int, int) = add; // fp is a pointer to a function (int,int)->int int result = fp(3, 4); // call through pointer // Passing functions as arguments void apply(int a, int b, int (*op)(int, int)) { printf("%d\n", op(a, b)); } // typedef cleans it up typedef int (*BinOp)(int, int); BinOp op = add;
Pointer to Struct / Arrow Operator
struct Point { int x; int y; }; struct Point pt = {1, 2}; struct Point *pp = &pt; pp->x = 10; // same as (*pp).x = 10 printf("%d %d\n", pp->x, pp->y); // Dynamic struct struct Point *dp = malloc(sizeof(struct Point)); dp->x = 5; dp->y = 7; free(dp);
Common Pointer Patterns
Output parameters
// Return status; write result via pointer int parse_int(const char *s, int *out) { char *end; long v = strtol(s, &end, 10); if (end == s) return -1; // parse failure *out = (int)v; return 0; } int n; if (parse_int("42", &n) == 0) printf("%d\n", n);
Linked list node
struct Node { int data; struct Node *next; }; struct Node *head = NULL; struct Node *new_node(int val) { struct Node *n = malloc(sizeof *n); n->data = val; n->next = NULL; return n; }
Pointer to VLA (C99+)
int n = 5; int arr[n]; int (*p)[n] = &arr; // pointer to a VLA of n ints (*p)[0] = 42;
Pointer Pitfalls
// Dangling pointer — pointing to freed/expired memory int *bad(void) { int local = 5; return &local; // UB: local destroyed on return } int *p = malloc(10); free(p); *p = 1; // UB: use-after-free // Double free free(p); free(p); // UB // Fix: set to NULL after free free(p); p = NULL; // safe to free(NULL) — no-op // Uninitialized pointer int *up; *up = 5; // UB: up contains garbage address // Off-by-one int arr[5]; int *end = arr + 5; // valid (one-past-end), but *end is UB
restrict Qualifier (C99+)
// Tells compiler: no other pointer aliases this memory during this call // Enables better optimization void add_arrays(int * restrict dst, const int * restrict a, const int * restrict b, int n) { for (int i = 0; i < n; i++) dst[i] = a[i] + b[i]; }