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; and int *p; are identical. But int* a, b; declares a as int* and b as int — 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

DeclarationWhat's const?
const int *pThe int it points to (cannot modify *p)
int * const pThe pointer itself (cannot change p)
const int * const pBoth 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];
}