C Core Idioms Cheatsheet

Language-level patterns. Minimal abstraction. Bounds and ownership made explicit.

C11+

Strings: index ↔ pointer

IntentIndex stylePointer style
Scanwhile (s[i]) ++i;while (*p) ++p;
Parallel scana[i], b[i]*a++, *b++
Bounded scan/outputi < capp < end
Copydst[i] = src[i]*dst++ = *src++
Copy + test NULif (!(dst[i] = src[i]))if (!(*dst++ = *src++))
Read + advances[i++]*p++
Write + advancedst[i++] = c*p++ = c
Distancei(size_t)(p - start)
Filterread/write indexesread/write pointers

Bounded string copy

Index

size_t str_icopy(char *dst, size_t cap, const char *src) {
    if (cap == 0) return 0;

    for (size_t n = 0; n < cap; n++) {
        dst[n] = src[n];
        if (src[n] == '\0') return n;
    }

    dst[cap - 1] = '\0';
    return cap - 1;
}

Bounded string copy

Pointer

size_t str_pcopy(char *dst, size_t cap, const char *src) {
    if (cap == 0) return 0;

    char *p = dst;
    char *e = dst + cap;

    while (p < e) {
        if (!(*p++ = *src++))
            return (size_t)(p - dst - 1);
    }

    *--p = '\0';
    return cap - 1;
}

Arrays

IntentIndex stylePointer style
Traversefor (i = 0; i < n; i++)for (p = a; p < end; p++)
Push backa[n++] = x;*end++ = x;
Pop backx = a[--n];x = *--end;
Unordered removea[i] = a[--n];*p = *--end;
Ordered removeshift tail leftshift [p+1,end) left
Compact/filterseparate r,wseparate read,write
Remainingn - iend - p

Array compaction

T *write = a;

for (T *read = a; read < end; read++) {
    if (keep(*read))
        *write++ = *read;
}

end = write;

Unordered remove while scanning

for (T *p = a; p < end;) {
    if (remove(*p))
        *p = *--end;
    else
        p++;
}

After replacement, stay on p: the element moved from the back has not been examined yet.

Singly linked lists

IntentIdiom
Traversefor (Node *p = head; p; p = p->next)
Prependnode->next = head; head = node;
Findwhile (p && !match(p)) p = p->next;
Pointer-to-pointer cursorNode **pp = &head;
Advance link cursorpp = &(*pp)->next;
Remove current*pp = (*pp)->next;
Insert before currentnode->next = *pp; *pp = node;
Append locationwhile (*pp) pp = &(*pp)->next;

List remove while scanning

Node **pp = &head;

while (*pp) {
    if (remove(*pp))
        *pp = (*pp)->next;
    else
        pp = &(*pp)->next;
}

pp points to the link that points to the current node.

List reverse

Node *prev = NULL;
Node *p = head;

while (p) {
    Node *next = p->next;
    p->next = prev;
    prev = p;
    p = next;
}

head = prev;

Array stack

/* invariant: 0 <= top && top <= cap */

stack[top++] = x;  /* push */
x = stack[--top];  /* pop */
x = stack[top-1];  /* peek */

if (top == 0)   { /* empty */ }
if (top == cap) { /* full  */ }

Error handling

if (bad)
    return ERR_BAD;

int err;
if ((err = step1()) != 0)
    return err;

if ((err = step2()) != 0)
    return err;

Prefer guard clauses over deep nesting.

Cleanup

int err = -1;
A *a = NULL;
B *b = NULL;

a = acquire_a();
if (!a) goto cleanup;

b = acquire_b();
if (!b) goto cleanup;

err = 0;

cleanup:
release_b(b);
release_a(a);
return err;

Acquire forward. Release backward. One cleanup path.

Ownership transfer

dst->ptr = src->ptr;
src->ptr = NULL;

Transfer the resource, then clear the old owner.

First-element initialization

if (n == 0)
    return false;

T min = a[0];

for (size_t i = 1; i < n; i++) {
    if (a[i] < min)
        min = a[i];
}

Avoid arbitrary sentinels when the first element can seed the state.

Dynamic array

typedef struct {
    T *data;
    size_t len;
    size_t cap;
} Vec;

/* invariant */
0 <= len && len <= cap

/* grow before write */
if (len == cap)
    grow();

data[len++] = x;

Safe resize assignment

T *p = resize(v->data, new_cap);

if (!p)
    return error;

v->data = p;
v->cap = new_cap;

Do not overwrite the only live pointer until resizing succeeds.

Hash map: SoA + indexed slots + NIL

/* slot encoding */
0         /* NIL / empty */
SIZE_MAX  /* tombstone */
1..len    /* live dense indices */

/* dense or parallel storage */
Key   keys[data_cap];
Value values[data_cap];

/* index 0 is reserved */
keys[0]    /* NIL key */
values[0]  /* NIL value */

Using index 0 as NIL makes zero-initialized indices immediately valid and removes index translation: a live slot directly contains the dense index.

Hash lookup/probe

size_t k = hash(key) & (table_cap - 1);

for (;;) {
    size_t i = slots[k];

    if (i == 0)
        return 0;  /* NIL / not found */

    if (i != SIZE_MAX &&
        key_equal(&keys[i], key))
        return i;

    k = (k + 1) & (table_cap - 1);
}

Dense storage removal

keys[i] = keys[len];
values[i] = values[len];
len--;

/* then repair whichever table slot
   referred to the moved element */

With index 0 reserved, live elements occupy 1..len. Unordered removal can still move the last live element into the removed slot.

ZII / NIL-index design

0 == NILZero-initialized indices naturally mean “no object”.
{0}Structures containing indices begin in a valid empty state without custom initialization.
items[0]Reserve a real sentinel object so NIL reads can remain memory-safe.
return 0Lookup can return NIL instead of a separate error sentinel.
1..lenLive objects use nonzero indices; zero remains globally meaningful.
SIZE_MAXAn out-of-band tombstone remains available for open addressing.
typedef struct {
    size_t parent;
    size_t first_child;
    size_t next_sibling;
} Node;

/* valid empty state */
Node node = {0};

A NIL object makes access at index 0 safe, but a raw write to items[0] is not automatically a semantic no-op: it mutates the sentinel. If NIL writes must be discarded, enforce that in the mutation operation.

High-value patterns to internalize

p/endHalf-open pointer range: [p,end).
read/writeFiltering, compaction, decoding, transformation.
a[n++] / a[--n]Count doubles as next-free position.
*p = *--endO(1) unordered removal.
Node **ppManipulate the link to the current node; eliminates head special cases.
guard clausesReject errors early; keep the main path shallow.
goto cleanupCentralize partial-resource cleanup.
temporary resize pointerPreserve ownership across allocation failure.
0 == NILZero-is-initialization for index-based structures.
0 / SIZE_MAX / 1..lenNIL/empty, tombstone, and direct live indices for hash slots.