C Cheatsheet — A Concise Reference
A concise, self-contained reference for C (C11/C17): syntax, pointers, memory, strings, structs, and file I/O, plus threads, sockets, regex, time, processes, and build/debug essentials. Covers around 80% of day-to-day needs.
C C17 (ISO/IEC 9899:2018)
ISO C · Procedural · manual memory management · Static, weakly typed (with casts)
Recommended Learning Path
New to C? Read in this order: 1. Hello World — compile, run, exit codes 2. Variables & constants — types, sizes, const 3. Pointers & arrays — &, *, ->, pointer arithmetic 4. Control flow — if / switch / loops 5. Functions & variadics — pass-by-value vs pass-by-pointer 6. Strings & conversions — printf format table Consult File I/O and Error Handling when you need them. Once you're comfortable, return for structs, OO-style patterns, and the advanced sections. Have a specific task? Use the search box above — try "malloc", "socket", "pthread", "qsort", or "regex".
1.Hello World & the Build Environment
Compile, run and organize C programs: toolchain, IDE choice, multiple files, command-line arguments, exit codes.
Minimal Program
Every C program runs starting at main() and returns an int status to the OS: 0 means success, anything else means error. The #include at the top brings in declarations so the compiler knows what printf is.
Compile & Run (gcc / clang)
C is compiled rather than interpreted: the compiler translates source into an executable, which you then run. Always enable warnings (-Wall -Wextra) and pin the language standard (-std=c17) so portability issues surface at compile time.
Toolchain & IDE Checklist
You need a compiler plus an editor. Common setups: gcc on Linux, clang on macOS, MinGW-w64 or MSVC on Windows — all usable from VS Code or CLion. Run the version command once to confirm the toolchain works before debugging anything else.
Command-Line Arguments
main can receive what the user types on the command line: argc is the count, argv[] is the array of strings — argv[0] is always the program name. Treat arguments as strings and convert to numbers with strtol/strtod when needed.
Multiple Files
Large programs are split into multiple .c files, with a shared .h header for declarations. Each .c is compiled separately and then linked; the header keeps declarations and definitions in sync so any signature change shows up everywhere it's used.
Exit Codes
The value main returns goes to the shell: 0 (EXIT_SUCCESS) for success, anything else for failure. Scripts and CI depend on this convention, so pick meaningful exit codes. Check with $? on Linux/macOS and %ERRORLEVEL% on Windows.
Environment Variables
getenv() reads a variable from the process environment and returns its value, or NULL if unset. It's the standard way to inject configuration without rebuilding — but treat the result as untrusted input.
Preprocessor Basics
The preprocessor runs before compilation: #define for macros, # and ## to splice tokens, #ifdef/#if for conditional code. It's a typeless, plain-text layer — prefer real functions and const in most cases.
2.Variables & Constants
Declarations, type sizes, fixed-width integers, const placement, storage classes, typedef, and enumerations.
Basic Declarations
A declaration writes the type first, then the name, and initializes at the declaration site (C99+). Initialize when you declare, so you don't later read uninitialized garbage.
Type Sizes (Typical 64-bit Values)
C guarantees only minimum sizes — int may be 16 or 32 bits across platforms. Use sizeof to print the actual size on the target, and never hard-code byte counts.
Fixed-Width Types (C99+, <stdint.h>)
When memory layout must be exact — file formats, network protocols, hashes — reach for int32_t / uint64_t from <stdint.h>. They exist only when the platform can actually provide them, which is exactly why exact-width names are favored.
Unsigned / Signed
Unsigned types are never negative and wrap around mod 2^N; signed types can be negative. Mixed in one expression, the signed operand is promoted to unsigned — a classic pitfall source (see the faq section).
const — Read-Only (Compile-Time Check)
const makes a variable read-only; the compiler rejects accidental writes at build time. Tag everything that shouldn't change with const — it documents intent and helps the optimizer.
const Pointer Positions (A Classic Confuser)
In a pointer declaration, const's position decides what it protects: before * protects the pointee, after * protects the pointer itself. Read the type right-to-left and the difference between these two lines becomes clear.
Storage Classes
A storage class decides where a variable lives and how long. auto is the default for locals; static at file scope makes a name file-local; static on a local preserves its value across calls; extern refers to a name defined elsewhere.
typedef — A Type Alias
typedef gives a new name to a type. It doesn't create a new type, only an alias — but the alias keeps types short and consistent, hiding struct and function-pointer noise behind one readable word.
enum — Named Constants
enum names a set of related integer constants. Values start at 0 and step by 1, but you can assign explicit values or specify a range. Enums document themselves and work naturally with switch.
Designated Initializers (C99+)
C99 lets you initialize members by name (.port = 8080) or array elements by index ([3] = 9), in any order. Members you don't mention are zeroed automatically — far clearer than a long positional list.
Compound Literals (C99+)
A compound literal uses a cast-like syntax to inline-construct a temporary struct/array value. Convenient when you want to pass a one-shot value to a function without first declaring a named variable.
volatile (Memory-Mapped I/O / Signals)
volatile tells the compiler a variable may change outside the program's normal flow — a hardware register, a signal handler, or another thread. It stops the compiler from caching or reordering accesses.
3.Structs, Unions & Aggregate Types
Struct layout, unions, bit-fields, sizeof, _Static_assert, and alignment — the building blocks of real-world C data.
struct — Heterogeneous Records
A struct groups related values of different types into one record. Members are laid out in declaration order with alignment padding inserted between them, so sizeof(struct) is often larger than the sum of member sizes.
Pointer to struct — Access Members with ->
The arrow p->member is shorthand for (*p).member, dereferencing a struct pointer to access a member. Passing structs by pointer is cheap (one word) and lets the function modify the caller's record.
Anonymous struct + typedef
You can attach a typedef to a tagless struct and get a clean type name from a single declaration. It's the conventional way to name frequently used record types, sparing you the struct keyword everywhere.
union — Overlapping Storage (Size = Largest Member)
A union places all members in the same memory; its size equals the largest member. Only the most recently written member holds a valid value — reading others is undefined behavior, so keep a tag field alongside it.
Bit-Fields — Packed Flags
Bit-fields pack small flags into the bits of an integer, saving memory for records filled with booleans. Layout is compiler-defined, so keep bit-fields within a single translation unit — don't cross ABI boundaries.
sizeof — Bytes Occupied by an Object
sizeof gives the number of bytes occupied by a type or object. For an array still in scope, sizeof a / sizeof a[0] yields the element count — but once the array decays to a pointer, the trick silently breaks (see faq).
_Static_assert (C11+) — Compile-Time Checks
_Static_assert(cond, msg) checks at compile time; if cond is false, the build fails. Use it to validate size and ABI assumptions the moment they're broken, rather than discovering them at runtime.
Alignment — _Alignas & _Alignof (C11+)
Alignment determines which addresses an object can occupy; the compiler pads structs so each member sits on its natural boundary. _Alignof reports the alignment value, _Alignas raises it when needed (SIMD, cache-line sharing).
Flexible Array Member (C99+) — Trailing Array in a struct
A struct may end with an unsized array (data[]) whose length is decided at allocation time: a single malloc covers the struct plus the extra bytes. It's the C idiom for buffers whose size is known only at runtime.
Enumerations as Distinct Types (C23)
C23 lets you pin an enum's underlying type (enum Status : unsigned char), so its size is no longer up to the compiler. Otherwise enums are compatible with int — which suffices for most code.
4.Pointers & Arrays
Address-of, dereference, pointer arithmetic, array decay, pointer vs array, const correctness.
Address-Of (&) and Dereference (*)
& produces a pointer to an object; * is the reverse — it reads or writes through the pointer. Together they let you access a value indirectly and modify it from another scope.
NULL — The Null Pointer
NULL is the pointer that points to nothing and differs from every valid address. Dereferencing it is undefined behavior — usually a crash — so check for NULL before use.
Pointer Arithmetic (Scaled by sizeof(*p))
Adding an integer to a pointer moves it by elements, not bytes — p + 2 skips two ints. The subscript p[i] is just *(p + i). Going past the array's end is undefined behavior.
Array Decay (Expression Becomes Pointer)
In most expressions, an array name becomes a pointer to its first element. The upshot: inside a function you can't recover the array's length from the parameter alone — you must pass the length alongside the array.
Pointer vs Array
A char array owns its bytes and is modifiable; a char* pointing at a string literal typically points to read-only memory. Writing through such a pointer is undefined behavior and often crashes.
Pointers of Multiple Levels
A pointer to a pointer (int**) adds one more level of indirection. It shows up in arrays of strings, 2-D structures, and output parameters where the function allocates memory and the caller frees it.
Function Pointers
A function pointer holds a function's address and lets you call it indirectly. It underpins callbacks, dispatch tables, and the object-oriented patterns covered in the oop section.
Array of Pointers (e.g. argv)
An array of char* is the idiomatic container for a list of strings; argv is one. Each element points to its own NUL-terminated string, and the array is just a row of pointers.
const Correctness
Declaring a pointer parameter const — like const char* s — is a promise that the function won't modify what it points at. Callers can then pass string literals and arrays safely, and the compiler enforces the promise.
Pointer to Array vs Array of Pointers
int *a[5] is an array of 5 pointers; int (*b)[5] is a pointer to an array of 5 ints. Parentheses change everything. Read complex declarations right-to-left, or feed them to cdecl.
5.Control Flow
if/else, ternary, switch, for, while, do-while, break/continue, goto, and setjmp/longjmp.
if / else if / else
Branch on a condition: the first branch whose condition is true runs; if none match, else runs. else-if chains test alternatives in order — first match wins.
Ternary (An Expression)
cond ? a : b is an expression, not a statement: it picks a or b by cond. Use it for compact value selection — if it doesn't fit on one line or hurts readability, switch to if/else.
switch — Falls Through Without break
switch dispatches on an integer value. Unless you break, execution falls through into the next case — either intentionally (stacked empty cases) or with each case broken. default handles unmatched values.
for — Declare Counter in init (C99+)
for(init; cond; step) is the standard counting loop. Declaring the counter in init (C99+) confines it to the loop body, avoiding clashes with outer variables of the same name.
while / do-while
while tests the condition before each iteration and may run zero times. do-while always runs its body once before testing — ideal for menus and prompts where you need to "ask at least once".
break / continue
break exits the innermost loop or switch immediately; continue jumps to the next iteration. Used well, they flatten nested logic and remove flag variables; used badly, they hide control flow — so prefer them sparingly.
goto — Escape Deep Nesting (Use Rarely and Carefully)
goto jumps to a labeled line. Its legitimate uses are escaping deep nesting and centralizing error cleanup — the goto-cleanup idiom. Beyond that, return or a flag is almost always clearer.
setjmp / longjmp — Non-Local Jumps
setjmp saves an execution context; longjmp restores it from anywhere, unwinding back to the save point. All cleanup in between is skipped — no destructors run — so it's usually not worth it compared to goto or an explicit return.
Loop Idioms
Worth knowing loop shapes: indexed loops over a count, pointer walks over an array, and infinite loops broken by break. Pick the one that most directly expresses the intent.
Logical Short-Circuit Evaluation
&& and || evaluate left-to-right and stop as soon as the result is determined. That's why ptr && ptr->x is safe: the second operand runs only if the first is true, so you can guard a dereference inline.
6.Functions & Variadic Arguments
Pass-by-value vs pass-by-pointer, function pointers and typedef, variadic arguments, recursion, and _Generic.
Pass-by-Value — Caller's Variable Stays Intact
All C arguments are passed by value — the callee gets a copy, and modifying it doesn't touch the caller's. Use this for plain scalars and small structs; switch to pointer form when you need to mutate or to spare large objects.
Pass-by-Pointer (Modify the Caller)
To modify a caller's variable, pass its address. The callee dereferences the pointer and writes through it, and the change is visible to the caller — that's how scanf and friends produce their output.
const Parameter — A Read-Only Contract
A const parameter like const char* s is a read-only contract: the function promises not to modify what it points at. Callers can safely pass string literals, and the API's intent is obvious.
Array Parameter Decays (Pass the Length)
An array parameter decays to a pointer and the length doesn't ride along. Always pass the length explicitly — otherwise the function has no way to know where the data ends.
Function Pointer + typedef
typedef a function-pointer signature once, then declare variables and parameters with the alias. Dispatch tables and callbacks stop being a wall of parentheses and asterisks.
Variadic — printf-Style Functions
Functions with ... in the signature take a variable number of arguments, like printf. Use va_start/va_arg/va_end to read them; a fixed first parameter must declare how many follow, or the call can't be safely decoded.
Variadic Forwarding — the v* Family
To forward variadic arguments to another variadic function (say a logging wrapper to vfprintf), use the v* family and pass the va_list itself. Re-reading the list with va_arg afterwards will corrupt it.
Recursion
A recursive function calls itself on a smaller instance of the same problem until it hits the base case. Each call takes stack space, so prefer iteration when depth is large or unbounded.
_Generic (C11+) — Compile-Time Type Dispatch
_Generic (C11) picks a result expression at compile time based on the controlling expression's type — a lightweight way to fake function overloading and write type-safe macros.
noreturn (C11+) and inline
noreturn marks a function as never returning (e.g. fatal-error routines), letting the compiler infer unreachable code. inline is a hint, not a guarantee — the compiler may still emit a real call.
7.Strings & Numeric Conversions
NUL-terminated strings, safe writes, strtol/strtok/sscanf conversions, and a printf format specifier table.
The Basics — A NUL-Terminated Character Array
A C string is a character array terminated by a NUL byte ('\0'). strlen counts up to the terminator; any buffer holding a string must reserve room for it, or writes will overflow.
Copy / Compare / Concatenate (Unsafe vs Safe)
strcpy/strcat don't check bounds — if the source is too long, they silently overflow. Prefer snprintf for bounded, always-NUL-terminated writes; strcmp/strncmp for comparisons; strncat copies at most n characters plus a terminator.
Searching
strchr finds the first occurrence of a character, strrchr the last, strstr the first occurrence of a substring. All return a pointer into the string (or NULL if not found).
mem* — Raw Bytes (Handles Any Memory, Including NUL)
The mem* family (memcpy, memmove, memset, memcmp) operates on raw bytes and handles embedded NULs. memcpy and memmove differ only in overlapping regions — use memmove when overlap is possible.
Numeric Conversion: strtol/strtod
Use strtol/strtod to parse numbers: they report errors and tell you where parsing stopped. atoi silently returns 0 on garbage, masking invalid input — don't use it in correctness-seeking code.
Robust Parsing — Validate the Entire String
A complete parse checks three things: errno not set after strtol, at least one digit consumed (end != s), and the entire string consumed (*end == '\0'). Skip any check and trailing garbage slips through.
Tokenizing: strtok_r (Reentrant)
strtok mutates the string in place, writing '\0' at each delimiter; the _r-suffixed version is reentrant (thread-safe). If you still need the original string, copy it first.
sscanf — Parse With a Format String (Returns Match Count)
sscanf parses a string with a format string and returns the number of fields successfully matched. Check the return value: if it's lower than expected, the input isn't shaped the way you thought.
printf Format Table
The printf family shares one format language: %d int, %ld long, %zu size_t, %f double, %s string, %p pointer, plus width and precision modifiers. A specifier must exactly match the argument's type — mismatch is undefined behavior.
Building Strings Safely
snprintf writes at most n bytes and is always NUL-terminated, so it's the safe way to assemble strings. For growing text, track the length and realloc the buffer, or use a small dynamic-buffer helper.
8.Arrays, Sorting & Searching
Array iteration, qsort/bsearch with comparators, 2-D arrays, and arrays of structs.
Fixed-Size Array — Iterate With Length
A C array is a fixed-size, contiguous run of elements. Iterate with a length: sizeof a / sizeof *a works while the real array is still in scope, but the size vanishes as soon as you pass it to a function.
Initialization Patterns
Initialize arrays positionally, by designated index ([2] = 9), or zero out the whole thing with {0}. A 2-D array is an array of arrays, stored in row-major order: m[i][j] is contiguous within its row.
qsort — Sort With a Comparator
qsort sorts in place using a comparator that returns <0, 0, or >0. The comparator receives pointers to elements, so cast the void* arguments back to the element type before comparing (and avoid overflow).
bsearch — Binary Search a Sorted Array
bsearch does a binary search — but only on a sorted array. It returns a pointer to a matching element, or NULL when none exists. On large arrays, sort then search beats linear scan.
String Comparator (Array of char*)
Sorting an array of char* must compare the strings, not the pointer values. The comparator casts to char* const*, dereferences to get each string pointer, then calls strcmp.
Array of Structs — Sort by a Field
To sort an array of structs by one field, write a comparator that takes two struct pointers and compares that field (e.g. a 3-way compare of a->key with b->key). qsort then rearranges the whole records.
Pointer-to-Array Parameter (Keeps 2-D Shape)
When passing a 2-D array to a function, the parameter type must include the column count — int m[][COLS] — so the compiler can compute each row's start. The first dimension (row count) may vary freely.
Dynamic Array Pattern (see also mem section)
A growable collection is a malloc'd block, realloc'd when full: capacity and length are tracked separately, growth doubles the size, free when done. This is the C counterpart of higher-language vectors and lists.
Variable-Length Arrays (C99, Optional)
A VLA is allocated on the stack using a runtime value (int vla[n]). It's optional in C11, and a large n can blow the stack — prefer malloc for large or portable needs.
2-D Arrays — Row-Major Order
int m[ROWS][COLS] is a contiguous row-major block; m[i][j] is *(*(m + i) + j). Subscripting needs to know the row width to compute each row's start, so the shape must be fixed at compile time.
9.Dynamic Memory & Ownership
malloc/calloc/realloc/free, stack vs heap, alignment, ownership rules, and the goto-cleanup pattern.
Allocate & Free
malloc returns uninitialized heap memory, and every allocation must eventually be freed. Always check for NULL before use; set the pointer to NULL after free, so a later double-free becomes a safe no-op.
calloc: count×size, Zero-Initialized
calloc(count, size) zero-initializes the memory, multiplies the two arguments for you, and detects overflow. Reach for it when you need initialized storage and know the element count.
realloc — Resize; May Move the Block
realloc resizes an allocation and may move the block to a new address. Capture the return value in a temporary and check: on failure the old block is still valid, but if you overwrite the pointer first you leak it.
Stack vs Heap
The stack is auto-managed, fast, reclaimed on return — but small and short-lived. The heap is manual, large, and survives the function. Put big, long-lived buffers on the heap.
Ownership — A Small Set of Rules
A minimal set of ownership discipline keeps memory problems under control: whoever allocates, frees; free each pointer once; set it to NULL after free; keep the old pointer when realloc fails. Convention is C's only resource manager.
Strings on the Heap
A heap string needs strlen(s) + 1 bytes — the extra one is the NUL terminator. malloc(strlen(s) + 1), then copy; forget the +1 and you have a one-byte overflow waiting to blow up.
struct With an Internal Pointer
A struct that owns heap data has two allocations to free: the inner buffer first, then the struct itself. Decide and document who owns the inner pointer, and free in reverse order of allocation.
goto Cleanup (Single Exit Point)
C has no destructors, so goto-cleanup is C's RAII: one exit point per function, one cleanup block that every path falls through to. Each resource is released exactly once on every path.
Alignment / Over-Alignment (C11)
aligned_alloc allocates with a specified alignment (SIMD, cache-line sharing); the size must be a multiple of the alignment. Plain malloc guarantees only the platform default alignment.
Memory Leak / Overflow Tooling (see build section)
Leaks, overflows, and use-after-frees hide until the process dies — use valgrind or AddressSanitizer to find them mechanically. Both are covered in the build section and are the cheapest safety nets you can add.
10.Object-Oriented Style Patterns
C is procedural, but structs + function pointers can emulate classes, inheritance, and polymorphism.
"Class" = struct + vtable
C's emulation of a class: a struct for the data, a table of function pointers (vtable) for the behavior. Methods are just plain functions taking an explicit self/this pointer.
Inheritance via Embedding
Embed the base struct as the first member and you get inheritance: a derived struct shares its address with its base, so a derived value can stand in anywhere a base is expected, and base methods work unchanged.
Constructor / Destructor Pairs
Pair an init function that allocates and initializes with a free function that releases and reclaims — C's version of constructors and destructors. Every created object must be destroyed exactly once.
Opaque Pointer — Hide the Implementation (PIMPL)
The header only forward-declares the struct; the definition lives in the .c file. Callers can hold the pointer but can't see inside — that's PIMPL: hide the implementation so internal changes don't break them.
Interface = a struct of Function Pointers
A struct stuffed with function pointers is an interface: any implementation that fills it can plug in, and each function takes a void* self for instance context. That's C's flavor of polymorphism.
Type-Erased Data (void *)
void* erases the type and lets one container hold heterogeneous values. To safely recover the type, you usually add a tag field recording what the pointer really points at.
Method Chaining by Returning *this
Each setter returns the object itself (*this), so calls chain: builder->setA(x)->setB(y). It makes builder-style configuration compact, at the cost of return values that callers can ignore.
Singleton (Lazy, Not Thread-Safe)
A lazy singleton allocates its instance into a static pointer on first use. This naive version isn't thread-safe — two threads can race on the first allocation — so add locking once it becomes shared.
Reference Counting (Simple Version)
retain/release give an object a reference count, freeing it when it hits zero. It's the foundation of Objective-C and for C objects it's a lighter ownership model than garbage collection.
Full OOP Is Possible (glib)
Full-blown OOP frameworks exist in C — glib and GObject prove it — but they bring real complexity. A minimal vtable plus an opaque pointer covers most needs; reach for the heavy artillery only when the project truly requires it.
11.Error Handling
No exceptions: errno, return codes + out parameters, goto-cleanup, error enums, and assertions.
errno After a Failing Call
Library and system calls set the global errno on failure; you must read or copy it immediately after the call — any later call can overwrite it. strerror(errno) turns the code into a human-readable message.
Return Code + Out Parameter (A Common C Idiom)
C's mainstream error idiom: return 0 on success, a negative error code on failure, and write the result through an out pointer. On failure the result pointer stays NULL/unchanged so the caller can check.
Error Enum — Explicit Error Categories
Enumerate the kinds of errors your function can produce — OK, INVALID_INPUT, NOMEM, IO — and return them. Named codes document themselves, work nicely in switch, and force you to think through every failure mode.
assert — For Debugging (Removed with NDEBUG)
assert(cond) aborts when cond is false, catching bugs during development. It's compiled out under -DNDEBUG, so never use it to guard behavior the user depends on — only invariants you assume true.
perror — Print errno Description
perror prints your message plus the current errno's description on one line of stderr. It's the fastest way to report why a system call failed — no formatting of errno required.
_Static_assert — Compile-Time
_Static_assert checks assumptions at compile time, not at runtime. Use it to validate sizes, offsets, and ABI constraints so they don't fail silently inside a production binary.
Thread-Safe strerror_r (POSIX)
strerror is not thread-safe — it overwrites its own internal buffer. strerror_r (POSIX) writes into a caller-provided buffer, so concurrent threads each get their own message.
Check System-Call Return Values
System calls fail — files, sockets, memory all do. Ignoring the return value turns a small failure into a later crash or data corruption; even successful fread/fwrite may return short counts, so always check.
Distinguishing Error from EOF with ferror/feof
When a read loop ends, ferror() and feof() tell you why — a real I/O error or end-of-file. EOF is a normal state, not an error — distinguish them with ferror.
12.File I/O
stdio streams, line-by-line and whole-file reading, binary I/O, POSIX file descriptors, mmap, and directory traversal.
Open / Close
fopen returns a FILE* stream, or NULL on failure; fclose flushes and frees it. Modes are r/w/a plus + for read+write and b for binary. Never keep using the pointer after a failed open.
Read the Whole File (Small Files)
For small files: seek to end, ftell for the size, rewind, malloc size+1, then fread it all in. Confirm the bytes read match the request — the file may have changed between calls.
Read Line by Line (Idiomatic)
fgets safely reads a line into a fixed buffer, stopping at a newline or the buffer limit. Use strcspn to strip the trailing newline — it'll otherwise stay in the buffer. This is the idiomatic way to process text line by line.
Writing
fprintf/fputs/fputc write formatted text, strings, and single characters to a stream. Errors can surface at write time or during fclose, so check fclose's result to avoid missing a failed flush.
Format to a String First (Safe)
Format the whole output into a buffer with snprintf first, then write it once. This avoids interleaved writes from concurrent sources, lets you validate before writing, and keeps the write atomic.
Binary I/O — Read & Write Raw Bytes
fread/fwrite move raw bytes and suit fixed-size records: read exactly sizeof(record) and handle the return. A short read is either an error or EOF — use ferror/feof to tell which.
POSIX File Descriptors
Beneath stdio, Linux/macOS expose open/read/write/close on int file descriptors: finer control, no buffering, more pitfalls. Cross over with fdopen (fd to FILE*) or fileno (FILE* to fd).
mmap — Map a File Into Memory
mmap maps a file directly into your address space, so reads and writes become plain memory accesses — fast for large files. munmap releases it; the file can't be empty, and the mapped size is page-aligned.
Directory Traversal
opendir/readdir/closedir list directory entries one by one. Skip the implicit '.' and '..' entries, then use stat to check each name's file type and size.
stat — File Metadata
stat/lstat/fstat return file metadata — size, modification time, permissions, type. tmpfile() gives you an unnamed temp file that's auto-deleted on close, ideal for scratch storage.
13.Common Pitfalls
Ten classic C pitfalls — correct versions are tagged GOOD, incorrect ones BAD.
Buffer Overflow
Writing past the end of a fixed buffer corrupts adjacent memory and is a top-tier security bug. Bound every write: strncpy plus a manual NUL, snprintf, or fgets with a length. Sanitizers catch issues you'd miss by eye.
Use-After-Free / Double-Free
Use-after-free is undefined behavior and often exploitable; freeing the same memory twice corrupts the allocator. Set the pointer to NULL right after free — free(NULL) is a safe no-op.
Memory Leak
Every allocation must be freed on every path; leaks silently grow until the process dies. AddressSanitizer and valgrind report them by default — run them during development, not after the crash.
= vs ==
A single = is assignment; == is comparison. if (x = 0) assigns 0 and is always false — a silent, hard-to-spot bug. Compile with -Wall, and parenthesize assignments inside conditions.
Signed Overflow Is UB
Signed integer overflow is undefined behavior: compilers may assume it never happens and optimize accordingly. Widen to long long before summing large signed values, or use the unsigned wraparound you meant.
Uninitialized Variable
Reading an uninitialized local is undefined behavior, usually yielding garbage. Initialize every variable at its declaration — that's what gives the compiler's -Wuninitialized something real to flag.
Array Decay in sizeof
An array parameter inside a function is a pointer, and sizeof gives 8 (pointer size) instead of the array size. sizeof a / sizeof a[0] works only while the real array is still in scope — pass the length explicitly.
Off-by-One Error
Loop bounds are a classic off-by-one: n elements means iterate i < n (indices 0..n-1), not i <= n which steps one past the end. The last legal index of an n-sized array is n-1.
printf Format Mismatch
Mismatched format specifier and argument type is undefined behavior — %s paired with an int can crash outright. Use %zu for size_t, match %d / %ld / %lld to the type, and turn on -Wformat to catch mismatches.
Signed / Unsigned Comparison
When signed meets unsigned, the signed operand is promoted to unsigned and negative numbers become huge positives. Cast both sides to the same signed type explicitly, or just don't mix them.
14.Threads & Concurrency
pthread create/join, mutexes, condition variables, atomics, and C11 threads — compile and link with -pthread.
Build & Headers
Threaded programs compile with -pthread, which links the pthread library. Include <pthread.h> and treat every pthread_* return value as an error to check — these APIs don't set errno.
Create & Join
pthread_create starts a thread running a function that receives a void* argument; pthread_join blocks until it returns. The argument you pass in and the void* you get back are the channel for cross-thread data.
Pass Data Out via void*
A thread returns its result as void* — typically a pointer to a malloc'd value. The caller joins, casts the returned pointer back, reads the value, and frees it.
Mutex — Guard Shared State
Mutexes serialize access to shared state: lock before, unlock after. Keep critical sections short — locks are bottlenecks, and holding them invites deadlock.
Condition Variable — Wait / Signal
Condition variables let a thread sleep until another signals that some predicate has changed. Always wait in a while loop (spurious wakeups), hold the mutex while waiting, and re-check the predicate on wakeup.
Detached Thread — No Join Needed
A detached thread cleans up its own resources on exit and never needs joining. Only detach when you're sure you'll never wait on it — and never hand a detached thread a pointer into the caller's stack; it may already be gone.
C11 Threads (Portable)
C11's <threads.h> (thrd_t) is a thinner, portable alternative to pthreads — less capable, often just a thin wrapper over the OS API. On POSIX, pthreads remain the standard choice for serious work.
Atomics (C11) — <stdatomic.h>
atomic_int and atomic_fetch_add provide lock-free loads, stores, and increments with well-defined memory order — faster than a mutex for simple shared counters, and more correct than a plain int (no races).
Thread-Local Storage (C11)
_Thread_local (C11, GCC's __thread) gives each thread its own copy of a variable. It's great for per-thread caches and for error slots that must not clobber each other.
Error Handling in Threads
pthread functions return errno-style codes instead of setting errno. Capture the return value and format the diagnosis with strerror(rc) — unchecked failures usually surface as mysterious hangs or crashes.
Deadlock Avoidance
Deadlock arises when you "hold what you need and wait for what you don't". Lock in a fixed global order, keep critical sections small, prefer one lock per resource, and use timed locks when bounded waiting is required.
15.Networking (Sockets)
getaddrinfo, TCP client and server, a minimal HTTP request — no extra library linking needed on Linux/macOS.
Build & Headers
POSIX sockets need <sys/socket.h>, <netinet/in.h>, and <arpa/inet.h>; no extra linking on Linux/macOS. Use getaddrinfo to resolve addresses, not hand-filled sockaddr structs.
TCP Client — Resolve, Connect, Send/Recv
A TCP client uses getaddrinfo to resolve the server, then creates a socket and calls connect. The address list can be long — try each entry in turn, keep the first one that succeeds, and close the ones that fail as you go.
Send / Receive
send/recv move bytes on a connected socket but never guarantee one call does it all. Loop until the whole message is read or EOF, tracking how many bytes still remain.
TCP Server — The socket Flow
A server creates a socket, sets SO_REUSEADDR, binds a port, listens, and loops on accept. Each accept returns a new socket dedicated to that client — the listening socket is never read from.
Resolve Hostname -> IP
getaddrinfo turns a hostname and port into a list of candidate addresses, handling IPv4/IPv6 and service names along the way. Try them one by one until one succeeds, then free the list with freeaddrinfo.
Blocking vs Non-Blocking
Sockets are blocking by default: recv waits until data arrives. O_NONBLOCK makes the call return EAGAIN when nothing's ready, letting poll / epoll / select supervise many sockets from one thread.
UDP — sendto/recvfrom (Connectionless)
UDP is connectionless: sendto/recvfrom carry the destination address on each call — no handshake, no order, no delivery guarantee. Great for latency-sensitive traffic that can tolerate loss.
Byte Order — htons/htonl & Network Order
Network byte order is big-endian; your host may be little-endian. When filling sockaddr fields and reading numeric protocol fields off the wire, convert with htons/htonl/ntohs/ntohl.
Windows (Winsock) Differences:
Windows uses winsock2.h, requires WSAStartup before any socket use, and closes sockets with closesocket() instead of close(). The rest of the API matches POSIX sockets, so the code is largely portable.
Error Codes
Socket calls return -1 and set errno on failure — ECONNREFUSED, ETIMEDOUT, ECONNRESET — while getaddrinfo returns its own codes for gai_strerror. Check every step; a silent failure here becomes a hang later.
HTTPS Needs a TLS Library
Raw sockets only get you TCP. Encryption requires a TLS library — with OpenSSL, build an SSL_CTX, wrap the socket with SSL_new/SSL_connect, and use SSL_read/SSL_write — or use libcurl for a higher-level HTTP+TLS API.
16.Time & Date
Wall-clock time, UTC/local conversions, strftime formatting, and high-resolution monotonic timing.
Unix Epoch Seconds
time(NULL) returns the number of whole seconds since 1970-01-01 00:00 UTC (a time_t) — a timezone-free, universal instant that is what you want for storing timestamps.
Convert to UTC / Local struct tm
gmtime and localtime split a time_t into a struct tm (year, month, day, hour, ...) — in UTC and in the local timezone respectively. Mind the offsets: tm_year counts years since 1900 and tm_mon runs from 0 to 11.
Format With strftime
strftime formats a struct tm into readable text using %Y %m %d %H:%M:%S and friends — printf for dates. It is the standard way to build log lines and human-readable timestamps.
Parse a Date Back to time_t
strptime (POSIX) parses text into a struct tm, and mktime converts that into a time_t. Set tm_isdst to -1 so mktime works out daylight saving time itself instead of guessing wrong.
Elapsed Wall-Clock Time
difftime(b, a) returns the wall-clock seconds between two time_t values. It is simple and good enough for coarse durations, but the wall clock can jump — use a monotonic clock when you are measuring.
High-Resolution Monotonic Clock
clock_gettime(CLOCK_MONOTONIC) measures elapsed time that is unaffected by NTP and daylight saving adjustments — use it for benchmarks, timeouts and any timing that must never jump.
Nanosecond Sleep (C11 <time.h>)
nanosleep suspends the thread with timespec (nanosecond) precision. When a signal interrupts it, it reports the remaining time so the call can go back and finish sleeping.
CPU Time Used by This Process
clock() returns the CPU time consumed by the current process, not wall-clock time. Comparing CPU time against wall-clock time shows whether a task is waiting on I/O rather than computing.
Get the Timezone Offset
tm_gmtoff (GNU/glibc) holds the local time's offset from UTC in seconds (positive east), letting you render an RFC 822-style +0800 offset without guessing.
ISO 8601 Without strftime
Use snprintf to assemble an ISO 8601 timestamp from the fields of a UTC struct tm. The output is locale-independent and unambiguous — ideal for APIs and logs.
17.Processes & Signals
fork, exec, wait, inter-process pipes, signal handling, and a minimal daemon.
Platform & Headers
fork/exec/wait and signals are POSIX (Linux/macOS); on Windows you use CreateProcess, which is a different model. Calls in this section need <unistd.h>, <sys/wait.h>, and <signal.h>.
fork — Clone the Current Process
fork clones the current process. Both copies resume from the same return point: the child sees pid 0, the parent sees the child's pid. The child should _exit() — returning from main would run the parent's cleanup and flush stdio twice.
exec — Replace the Process Image
exec replaces the current process image with another program: the pid stays the same, the code is fresh. It returns only on failure (then call _exit(127), the shell convention). Environment, file descriptors, and working directory are preserved.
fork/exec/wait to Run a Command
Running an external command is exactly what the shell does: fork (clone), execvp (swap image), waitpid (reap). WEXITSTATUS pulls out the child's exit code, WIFSIGNALED tells you whether it was killed by a signal.
Pipe — Child Writes, Parent Reads
pipe(fd) creates a pair of connected descriptors: one end writes, the other reads. After fork, each process closes the end it isn't using, and the read end sees EOF when the write end is closed.
Signal Handlers
signal() registers a handler for an asynchronous signal. Inside the handler, only call async-signal-safe functions — write, _exit, signal, kill — never printf or malloc, which are non-reentrant.
sigaction — A Robust API
sigaction is the robust replacement for signal(): it can block other signals while the handler runs, set SA_RESTART to auto-resume interrupted system calls, and tell the handler which signal fired. Prefer it for new code.
Block Signals Temporarily
sigprocmask defers signals around a critical section, then unblocks them — so handlers can't jump in while shared state is half-mutated. Pair it with a handler that processes "saved events" and signal use becomes safe.
Kill a Process / Check Existence
kill(pid, sig) sends a signal to a process; kill(pid, 0) just checks that the process exists and you may signal it, without actually delivering. A negative pid targets the whole process group.
Zombies — Reap Your Children
A child that exits before its parent waits becomes a zombie, holding a pid until wait/waitpid reaps it. Ignore SIGCHLD to let children be auto-reaped, or reap them explicitly in a handler.
Daemon Skeleton (Double fork)
A daemon detaches from the controlling terminal: fork so the child isn't the process-group leader, setsid to open a new session, fork again, chdir('/'), and redirect stdio to /dev/null or a log file.
18.POSIX Regular Expressions
regcomp/regexec/regerror: compile patterns, match and extract capture groups, iterate all matches.
Setup & Headers
POSIX regex (regcomp/regexec) ships in Linux/macOS libc — no extra library needed. With REG_EXTENDED you get ERE syntax; the default BRE is an older, different dialect.
Compile & Match
First regcomp compiles the pattern once (and check the return value!), then regexec matches against a string. Use regfree to release the compiled pattern — every regcomp pairs with one regfree.
Capture Groups
Parenthesized groups land in regmatch_t entries: m[1] holds the start/end offsets of group 1 within the matched text. Use the %.*s precision trick with the offset pair to print a captured slice.
Iterate All Matches (Loop With REG_NOTBOL)
To find all matches, loop regexec through the string, advancing past each match. After the first iteration pass REG_NOTBOL so ^ doesn't re-anchor mid-string.
Useful Flags
REG_EXTENDED enables ERE syntax (use it); REG_ICASE is case-insensitive matching; REG_NOSUB skips capture tracking for speed; REG_NEWLINE makes ^/$ match line boundaries instead of the whole string.
ERE Syntax Quick Reference
ERE quick reference: . any char, ^ $ anchors, [] character classes, * + ? repetition, {m,n} bounded repetition, (a|b) alternation, backreferences, and POSIX classes inside brackets like [[:alpha:]].
Replacement — None Built-in; Assemble Output by Hand
POSIX regex has no built-in replacement. Assemble the output: loop matches, copy the text between them, insert the replacement at each match — the same pattern as iterating all matches.
Escape User Input
To embed user input in a pattern, prefix each metacharacter ([\\^$.|?*+()[]{}) with a backslash. Otherwise a crafted input changes what your pattern means — this is an injection bug, not just a logic issue.
Check regcomp and Always regfree
regcomp fails on a bad pattern — always check the return value and format with regerror. And pair every regcomp with a regfree, or matching repeatedly inside a loop leaks compiled patterns.
Limits & Performance
Reuse a compiled regex_t — recompiling per match is wasted work. POSIX regex engines backtrack, and pathological patterns against untrusted input can be slow — for heavy or adversarial use, prefer PCRE2 or RE2.
Compile-Time Matching for switch-like Dispatch
Pre-compile patterns at startup and chain regexec calls in order. When the input can match several shapes, it reads like a clean routing table — and beats a wall of string compares.
19.Build & Debug
Compiler flags, a minimal Makefile, gdb, sanitizers, and valgrind — the tools that find the bugs the FAQ warns about.
Common gcc/clang Flags
-std=c17 pins the language, -Wall -Wextra turns on useful warnings (always on), -g adds debug info, -O0/-O2 picks optimization, -fsanitize=address,undefined adds runtime checks. Free, and catches real bugs.
Makefile — A Minimal Build Driver
A minimal Makefile puts the compiler, flags, target name, and object list into variables, leans on the implicit rule %.o:%.c, and provides a clean target. make rebuilds only what changed — that's the whole point.
Debug Build & Release Build
Debug builds with -g -O0 keep full symbols and no optimization; release builds with -O2 -DNDEBUG, which also compiles out assert. Keep both, so you can reproduce a crash under the exact config a user ran.
gdb Basics
gdb ./prog, then: break main, run, next/step through lines, print x to inspect, backtrace for the call stack, continue, quit. Pair with -batch -ex to dump crashes from CI non-interactively.
AddressSanitizer (ASan)
Compile with -fsanitize=address,undefined and the program reports overflows, use-after-free, and leaks at runtime with precise stack traces. Faster and clearer than valgrind — the first tool to turn on.
valgrind Memory Check
valgrind --leak-check=full ./prog reports invalid reads/writes, use-after-free, and definite leaks with no recompile — but it's 20-50× slower. Use it when you can't rebuild or need more detail.
UBSan Flags
-fsanitize=undefined catches signed overflow, misaligned access, and other undefined behavior at runtime with precise reports. Combine with -fsanitize=address for one build that covers both memory and UB.
Compile to Assembly / Preprocess
gcc -S emits assembly, gcc -E shows the preprocessed source, gcc -c produces a non-linked object file. Each is a small window onto what the compiler and preprocessor actually did.
Static Analysis (Optional)
gcc -fanalyzer, clang --analyze, and cppcheck find bugs without running the program — null dereferences, leaks, uninitialized uses. Cheap to add to CI, and they catch bug classes tests miss.
Build Systems
Make fits small to medium projects; CMake generates Make or Ninja files and is portable across platforms; Meson is a faster, more modern alternative. For a single file, a Makefile or even a shell alias is enough.
Official Links
Direct links to the official docs and resources.
About this Cheatsheet
This page is a self-contained cheatsheet for ISO C (C11/C17), covering the language and the parts of the standard library that handle around 80% of systems-programming day-to-day use, plus the concurrency, sockets, regex, and build tools real projects actually rely on. For authoritative references, see the C11 standard (ISO/IEC 9899:2011) and the C section of cppreference. 19 sections, each focused on a single theme — from your first program and pointers, through strings, memory ownership, and common pitfalls. Each section splits into 6-10 sub-topics with 5-15 line snippets. Code blocks are deliberately short and self-explanatory, with a copy button on each so you can paste them straight into a compiler. Everything runs in your browser — no uploads, no tracking. This page is part of GuruToolkit's free developer-toolset collection. The snippets here are free to use, with no warranty.
Version 2.3.0