The Ultimate Reference

C Programming Cheat Sheet

From "wtf is a variable" to "I work at NASA now." Every concept explained like you're human, not a textbook.

Baby Basics Getting Somewhere Actually Cooking Deep Shit Black Magic NASA Rules
Found 0 matching cards
1
Baby Basics
You literally just installed a compiler. Congrats.
Hello World — Your First Program
// every C file needs this. stdio.h = standard input/output.
// without it you literally can't print shit to the screen
#include <stdio.h>

// int main() is THE entry point. program starts here, period.
// return 0 means "hey OS, everything went fine, chill"
int main() {
    // printf = "print formatted". \n is a newline (enter key basically)
    printf("Hello, World!\n");
    return 0;
}
Compile it: gcc hello.c -o hello then run: ./hello — that's literally it.
Data Types — What Kind of Crap You're Storing
TypeSizeWhat it holds
int4 bytesWhole numbers. No decimals, don't even try.
float4 bytesDecimals. ~7 digits of precision. Gets sketchy fast.
double8 bytesBetter decimals. Use this over float unless you hate memory.
char1 byteSingle character. Like 'A' or '$'. Yes it's tiny.
long8 bytesBig-ass integers. When int just isn't enough.
short2 bytesTiny int. You'll rarely use this but it exists.
unsigned int4 bytesOnly positive ints. Double the max value tho.
void0Nothing. "This function returns nothing, shut up."
Variables — Boxes That Hold Your Stuff
// format: type name = value;
// think of it like labeling a box before you put shit in it
int age = 17;
float gpa = 3.7;
char grade = 'A';      // single quotes for char, don't mess this up
double bigNum = 9999999.99;

// you can declare without assigning (it'll have garbage value lol)
int x;        // x = some random bullshit number. be careful.
x = 42;       // now x is 42. finally.

// const = you CAN'T change it later. compiler will yell at you.
const float PI = 3.14159;
printf & scanf — Talking To / Hearing From the User
// format specifiers: how you shove variables into strings
int score = 100;
float temp = 98.6;
char letter = 'B';

printf("Score: %d\n", score);     // %d = integer
printf("Temp: %.1f\n", temp);     // %.1f = float, 1 decimal place
printf("Grade: %c\n", letter);    // %c = char
printf("Name: %s\n", "Chad");     // %s = string

// scanf reads user input. THE & IS NOT OPTIONAL.
// & means "address of". forget it = segfault = your program dies
int num;
scanf("%d", &num);   // reads an int the user types
Always put & before your variable in scanf or it WILL crash. No exceptions.
Operators — Math & Logic Stuff
// arithmetic — basic ass math
int a = 10, b = 3;
printf("%d\n", a + b);   // 13
printf("%d\n", a - b);   // 7
printf("%d\n", a * b);   // 30
printf("%d\n", a / b);   // 3 — INT DIVISION. no decimal. R.I.P. the .33
printf("%d\n", a % b);   // 1 — modulo = remainder. super useful btw

// shorthand — because typing is exhausting
a++;           // a = a + 1  (same as a += 1)
a--;           // a = a - 1
a += 5;        // a = a + 5
a *= 2;        // a = a * 2

// comparison — returns 1 (true) or 0 (false)
a == b   // equal? (NOT = which is assignment, don't mix these up)
a != b   // not equal?
a > b    // greater than?
a <= b   // less than or equal?

// logical — combining conditions
a > 0 && b > 0   // AND — both must be true
a > 0 || b > 0   // OR — at least one must be true
!(a > 0)          // NOT — flips it
If / Else — Making Decisions Like a Normal Person
int grade = 75;

// if the condition in () is true, run the {} block
if (grade >= 90) {
    printf("You're a genius or you cheated\n");
} else if (grade >= 70) {
    printf("Decent. Could be worse.\n");
} else if (grade >= 60) {
    printf("Barely passing lmao\n");
} else {
    printf("Bro... study more\n");
}

// ternary operator — if/else on one line (flex move)
// condition ? value_if_true : value_if_false
int max = (grade > 50) ? grade : 50;
printf("Max of grade/50: %d\n", max);
2
Getting Somewhere
Loops, functions, arrays — now you're actually doing stuff.
Loops — Repeat Shit Without Copy-Pasting
// FOR loop — when you know how many times to loop
// for (start; keep going while; do this each time)
for (int i = 0; i < 5; i++) {
    printf("Loop #%d\n", i);  // prints 0 1 2 3 4 (NOT 5, < not <=)
}

// WHILE loop — when you don't know how many times
int count = 0;
while (count < 3) {
    printf("Still going: %d\n", count);
    count++;   // DON'T FORGET THIS. infinite loop = freeze = sadness
}

// DO-WHILE — runs at least once, THEN checks condition
do {
    printf("I ran at least once no matter what\n");
    count++;
} while (count < 1);

// break = GTFO the loop immediately
// continue = skip this iteration, keep looping
for (int i = 0; i < 10; i++) {
    if (i == 5) break;      // stops at 5
    if (i % 2 == 0) continue; // skips even numbers
    printf("%d ", i);         // prints: 1 3 (evens skipped, stops at 5)
}
Switch — Cleaner Than a Million If/Elses
int day = 3;

switch (day) {    // checks day against each case
    case 1:
        printf("Monday. Ugh.\n");
        break;  // ALWAYS break or it falls through to next case
    case 2:
        printf("Tuesday. Still rough.\n");
        break;
    case 3:
        printf("Wednesday. Hump day lol.\n");
        break;
    default:           // like the "else" — catches everything else
        printf("Some other day idk\n");
}

// fall-through trick — when two cases do the same thing
switch (day) {
    case 6:   // no break here, falls through to case 7
    case 7:
        printf("WEEKEND BABY LET'S GO\n");
        break;
}
Missing a break is one of the most classic C bugs ever. Don't be that person.
Functions — Reusable Chunks of Logic
// define a function BEFORE main() or declare it as a prototype
// returnType functionName(param1, param2) { ... }

// this function takes two ints and returns their sum
int add(int a, int b) {
    return a + b;   // sends the value back to whoever called it
}

// void functions return nothing — just DO stuff
void greet(char name[]) {
    printf("Hey %s, what's up!\n", name);
}

// function prototype — declare it up top so you can define it anywhere
double power(double base, int exp);  // just the signature, no body

int main() {
    int result = add(5, 3);  // result = 8
    greet("Alex");
    printf("2^10 = %.0f\n", power(2.0, 10));
    return 0;
}

// defined down here because we prototyped it above
double power(double base, int exp) {
    double result = 1.0;
    for (int i = 0; i < exp; i++) result *= base;
    return result;
}
Arrays — Lists of the Same Type of Stuff
// array = a fixed-size list. all same type. indexed from 0.
int scores[5] = {95, 87, 76, 92, 83};

// access by index. first element = [0]. last = [4] NOT [5].
// going out of bounds = undefined behavior = random chaos. BE CAREFUL.
printf("%d\n", scores[0]);  // 95
printf("%d\n", scores[4]);  // 83

// loop through array — this is the standard move
int len = 5;
for (int i = 0; i < len; i++) {
    printf("Score %d: %d\n", i, scores[i]);
}

// 2D array — like a grid or table
int grid[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};
printf("%d\n", grid[1][2]);  // row 1, col 2 = 6

// get array size at compile time (only works in same scope it's declared)
int size = sizeof(scores) / sizeof(scores[0]);  // 20/4 = 5
Strings — Just Arrays of Chars (It's Annoying, I Know)
#include <string.h>  // need this for string functions

// strings are char arrays ending with '\0' (null terminator)
// C literally does NOT have a String type. it's always this.
char name[20] = "Alex";   // stored as: A, l, e, x, \0 + garbage
char city[] = "NYC";     // compiler figures out the size (4 bytes: N,Y,C,\0)

// useful string functions from string.h
strlen(name);            // length of string (NOT counting \0)
strcpy(name, "Jordan");  // copy "Jordan" into name. DON'T overflow the buffer!
strcat(name, " Smith"); // append " Smith" to name. again, watch size.
strcmp(name, "Alex");   // compare. 0 = equal, neg = less, pos = greater

// NEVER use == to compare strings. it compares addresses, not content.
// that's one of those bugs that'll haunt you for hours lmao
if (strcmp(name, "Alex") == 0) {
    printf("Same name!\n");
}
Don't use == on strings. Use strcmp(). Seriously. This trips up everyone.
Scope — Where Variables Actually Exist
// global variables — accessible from ANY function in the file
// generally avoid using these. it gets messy fast.
int globalCount = 0;

void increment() {
    globalCount++;  // can see globalCount because it's global

    // local variable — ONLY lives inside this function
    int localVar = 10;
}  // localVar is GONE after this }. it doesn't exist anymore.

// static local — keeps its value between function calls
void counter() {
    static int count = 0;  // initialized ONCE, persists between calls
    count++;
    printf("Called %d times\n", count);
}
3
Actually Cooking
Pointers, structs, memory — the real C experience begins.
Pointers — The Most Confusing Shit in C (It Clicks, I Promise)
// every variable lives at some memory address. a pointer STORES that address.
// think: pointer is a sticky note with someone's home address on it

int x = 42;
int *p;          // p is a pointer to an int. the * means "pointer to"
p = &x;          // & means "give me the address of x". now p points to x.

printf("%d\n", x);    // 42 — the actual value
printf("%p\n", p);    // some address like 0x7fff... — memory location
printf("%d\n", *p);   // 42 — *p = "go to that address and get the value" = dereference

// changing x THROUGH the pointer
*p = 100;   // x is now 100. wild right? we changed x without touching x directly.
printf("%d\n", x);  // 100 ← yep, changed.

// NULL pointer — pointer that points to nothing. safe default.
int *ptr = NULL;
if (ptr != NULL) {   // ALWAYS check before dereferencing or you segfault
    printf("%d\n", *ptr);
}
Dereferencing a NULL or uninitialized pointer = segmentation fault = your program is dead. Check first.
Pointer Arithmetic — Navigating Memory Like a GPS
// arrays and pointers are SUPER related in C
// the array name IS a pointer to the first element
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;   // p points to arr[0]

printf("%d\n", *p);         // 10 — arr[0]
printf("%d\n", *(p+1));     // 20 — arr[1]. p+1 moves forward by sizeof(int)
printf("%d\n", *(p+4));     // 50 — arr[4]

// arr[i] and *(arr+i) are literally identical. C translates one to the other.
// this is why arrays start at 0 — it's an OFFSET from the start address

p++;   // moves p forward by sizeof(int) bytes. now points to arr[1]
printf("%d\n", *p);  // 20

// pointer to pointer — yes this is a thing and yes it's annoying
int **pp = &p;   // pp points to p which points to x. pointer inception.
printf("%d\n", **pp);  // dereference twice to get the actual value
Structs — Grouping Related Data Together Like a Class (But Simpler)
// struct = a custom data type made of other types
// like if you want to store info about a person in one place
struct Person {
    char name[50];
    int  age;
    float gpa;
};

// or use typedef so you don't have to type "struct" every time
typedef struct {
    char name[50];
    int  age;
    float gpa;
} Person;

Person p1;                      // now no need to write "struct Person"
strcpy(p1.name, "Maya");        // dot notation to access fields
p1.age = 17;
p1.gpa = 3.9;

// or initialize all at once
Person p2 = {"Jordan", 18, 3.5};

// struct pointer — use -> instead of dot when you have a pointer to a struct
Person *ptr = &p1;
printf("%s is %d years old\n", ptr->name, ptr->age);
// ptr->name is the same as (*ptr).name. -> is just cleaner.
Dynamic Memory — Asking the OS for RAM at Runtime
#include <stdlib.h>  // need this for malloc, calloc, realloc, free

// malloc — allocate N bytes of memory. returns a void pointer (cast it)
// this is memory on the HEAP, not the stack. it persists until you free it.
int *arr = (int*) malloc(5 * sizeof(int));  // space for 5 ints

// ALWAYS check if malloc returned NULL (means system is out of memory)
if (arr == NULL) {
    printf("malloc failed. system is cooked.\n");
    return 1;
}

// use it like a normal array
for (int i = 0; i < 5; i++) arr[i] = i * 10;

// calloc — like malloc but ZEROES out the memory. safer.
int *arr2 = (int*) calloc(5, sizeof(int));  // 5 ints, all set to 0

// realloc — resize existing allocation (like array.push in JS but manual)
arr = (int*) realloc(arr, 10 * sizeof(int));  // now space for 10 ints

// FREE IT when you're done. EVERY malloc needs a free.
// not freeing = memory leak = program slowly eats your RAM. BAD.
free(arr);
free(arr2);
arr = NULL;    // set to NULL after freeing. prevents use-after-free bugs.
Every malloc/calloc/realloc must have a matching free(). No exceptions. Memory leaks are silent killers.
File I/O — Reading & Writing Files Like a Pro
#include <stdio.h>

// fopen opens a file. modes: "r"=read, "w"=write (creates/clears),
// "a"=append, "rb"/"wb" = binary read/write
FILE *f = fopen("data.txt", "w");

if (f == NULL) {   // check this. file might not exist or no permission.
    perror("Couldn't open file");  // perror prints system error message
    return 1;
}

fprintf(f, "Hello File!\n");   // printf but to a file
fprintf(f, "Score: %d\n", 100);
fclose(f);   // ALWAYS close the file when done. flushes buffer.

// reading from a file
f = fopen("data.txt", "r");
char line[100];
while (fgets(line, sizeof(line), f) != NULL) {  // reads line by line
    printf("%s", line);   // fgets includes the \n so don't add another
}
fclose(f);
Enums & Typedef — Making Code Actually Readable
// enum = named constants. way better than magic numbers like 0, 1, 2
// instead of if (status == 2) you write if (status == RUNNING) — clear!
typedef enum {
    IDLE,     // = 0 automatically
    RUNNING,  // = 1
    STOPPED,  // = 2
    ERROR     // = 3
} Status;

Status s = RUNNING;

switch (s) {
    case IDLE:    printf("Chillin'\n"); break;
    case RUNNING: printf("Go go go!\n"); break;
    case ERROR:   printf("Something broke lol\n"); break;
    default: break;
}

// typedef — give any type a new name (usually used to simplify ugly types)
typedef unsigned long long u64;  // now u64 instead of "unsigned long long"
u64 bigNumber = 1000000000000ULL;
4
Deep Shit
Preprocessor, bitwise ops, function pointers — welcome to the deep end.
Preprocessor — Runs Before Your Code Even Compiles
// preprocessor directives start with #. they run before compilation.

// #define — text substitution. no type checking. no semicolon.
#define MAX_SIZE   100
#define PI         3.14159
#define SQUARE(x)  ((x) * (x))  // function-like macro. ALWAYS wrap in () or bugs happen
// SQUARE(3+1) without parens would be 3+1*3+1=7 not 16. wrap it.

// include guards — prevent the same header from being included twice
// put this at the TOP of every .h file you write
#ifndef MY_HEADER_H
#define MY_HEADER_H
// ... all your header content ...
#endif

// conditional compilation — include code only in certain builds
#define DEBUG 1

#ifdef DEBUG
    printf("[DEBUG] value = %d\n", someVar);  // only in debug builds
#endif

// predefined macros — free stuff the compiler gives you
printf("Line %d in file %s\n", __LINE__, __FILE__);
printf("Compiled: %s %s\n", __DATE__, __TIME__);
Bitwise Ops — Operating Directly on the Bits
// bits are the lowest level. 0s and 1s. this shit is FAST.
// used in embedded systems, flags, compression, encryption, etc.
unsigned int a = 0b1010;  // = 10 in decimal. 0b prefix = binary literal
unsigned int b = 0b1100;  // = 12 in decimal

a & b    // AND:  1010 & 1100 = 1000 (8).  bit on only if BOTH are 1
a | b    // OR:   1010 | 1100 = 1110 (14). bit on if EITHER is 1
a ^ b    // XOR:  1010 ^ 1100 = 0110 (6).  bit on if DIFFERENT
~a       // NOT:  flip every bit. ~1010 = ...11110101 (depends on int size)
a << 2   // left shift:  1010 << 2 = 101000 (40). multiply by 2^n
a >> 1   // right shift: 1010 >> 1 = 0101  (5).  divide by 2^n

// real use: bit flags — pack multiple true/false values into one int
#define FLAG_READ    (1 << 0)   // = 0001
#define FLAG_WRITE   (1 << 1)   // = 0010
#define FLAG_EXEC    (1 << 2)   // = 0100

int perms = FLAG_READ | FLAG_WRITE;      // set read + write
if (perms & FLAG_READ) printf("Can read\n");  // check if read is set
perms |= FLAG_EXEC;     // set exec flag
perms &= ~FLAG_WRITE;   // clear write flag
Function Pointers — Treating Functions Like Variables (Big Brain Stuff)
// functions have addresses too. you can store those addresses in pointers.
// this enables callbacks, dispatch tables, plugin systems — powerful stuff.

int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }

// syntax: returnType (*pointerName)(paramTypes)
int (*op)(int, int);  // op is a pointer to a function taking 2 ints, returning int

op = add;   // point to add function
printf("%d\n", op(3, 4));  // 7

op = mul;   // now point to mul
printf("%d\n", op(3, 4));  // 12

// use typedef to make it readable (the raw syntax is ugly as sin)
typedef int (*MathOp)(int, int);
MathOp ops[] = {add, mul};   // array of function pointers = dispatch table!
printf("%d\n", ops[0](5, 6));  // 11
printf("%d\n", ops[1](5, 6));  // 30

// passing function as argument (callback pattern)
void apply(int *arr, int n, int (*fn)(int)) {
    for (int i = 0; i < n; i++) arr[i] = fn(arr[i]);
}
Recursion — A Function Calling Itself (Mind = Blown)
// recursion = function calls itself. needs a BASE CASE or it loops forever.
// each call uses stack space. deep recursion = stack overflow. be careful.

// classic example: factorial. 5! = 5*4*3*2*1
int factorial(int n) {
    if (n <= 1) return 1;          // base case — stop here or we recurse forever
    return n * factorial(n - 1); // recursive case — calls itself with smaller n
}

// fibonacci — each number is sum of the two before it
int fib(int n) {
    if (n <= 1) return n;
    return fib(n-1) + fib(n-2);
    // this is simple but SLOW for large n — exponential time complexity
    // for serious use, use iteration or memoization instead
}

printf("%d\n", factorial(5));  // 120
printf("%d\n", fib(10));       // 55
Every recursive function NEEDS a base case. No base case = infinite recursion = stack overflow = crash.
Linked List — Structs Pointing to Other Structs. Chains of Data.
// linked list = each node holds data AND a pointer to the next node
// unlike arrays, size is dynamic. inserting/deleting is O(1) at the head.
typedef struct Node {
    int data;
    struct Node *next;   // pointer to the next node in the chain
} Node;

// create a new node on the heap
Node* newNode(int val) {
    Node* n = (Node*) malloc(sizeof(Node));
    n->data = val;
    n->next = NULL;   // tail of the list points to nothing
    return n;
}

// traverse the list and print every node
void printList(Node* head) {
    Node* cur = head;
    while (cur != NULL) {     // keep going until we hit the end
        printf("%d -> ", cur->data);
        cur = cur->next;   // move to next node
    }
    printf("NULL\n");
}

// build a small list: 1 -> 2 -> 3 -> NULL
Node* head = newNode(1);
head->next = newNode(2);
head->next->next = newNode(3);
printList(head);
Multi-File Projects — Headers & Splitting Code Up
/* ── math_utils.h ─────────────────────────────── */
// header file = declarations only. tells OTHER files what exists.
#ifndef MATH_UTILS_H   // include guard — prevents double-include
#define MATH_UTILS_H
int add(int a, int b);    // function prototype (declaration)
int subtract(int a, int b);
#endif

/* ── math_utils.c ─────────────────────────────── */
// source file = actual implementations
#include "math_utils.h"   // "" for your own files, <> for system headers
int add(int a, int b)      { return a + b; }
int subtract(int a, int b) { return a - b; }

/* ── main.c ───────────────────────────────────── */
#include <stdio.h>
#include "math_utils.h"
int main() {
    printf("%d\n", add(3, 4));      // 7
    printf("%d\n", subtract(9, 5)); // 4
    return 0;
}

// compile: gcc main.c math_utils.c -o myapp
// or use a Makefile to automate all this
5
Black Magic
Memory models, undefined behavior, volatile, inline assembly. Here we go.
Memory Layout — How Your Program Actually Lives in RAM
/*
  A C program's memory is divided into segments:

  HIGH ADDRESS
  ┌──────────────────────────────────────────┐
  │  STACK  — local variables, function args │  ← grows downward
  │           function call frames           │
  │  (fast, limited, automatic cleanup)      │
  ├──────────────────────────────────────────┤
  │  ...free space in between...             │
  ├──────────────────────────────────────────┤
  │  HEAP   — malloc/calloc allocations      │  ← grows upward
  │  (large, manual management, slow alloc)  │
  ├──────────────────────────────────────────┤
  │  BSS    — uninitialized global/static    │  (zeroed out at start)
  ├──────────────────────────────────────────┤
  │  DATA   — initialized global/static vars │
  ├──────────────────────────────────────────┤
  │  TEXT   — your actual compiled code      │  (read-only)
  LOW ADDRESS
  └──────────────────────────────────────────┘

  Stack overflow = when the stack grows into the heap. RIP program.
  Use heap (malloc) for large or dynamically-sized data.
  Use stack for small, short-lived local variables.
*/
int global = 5;        // DATA segment
int uninit;            // BSS segment (gets zeroed)
int main() {
    int local = 10;    // STACK
    int* h = malloc(4); // HEAP (the pointer itself is on stack though!)
    free(h);
}
Undefined Behavior — The Universe's Rules Don't Apply Here
// "Undefined Behavior" (UB) = C standard literally doesn't define what happens
// it might work, crash, corrupt data, or make demons fly out of your nose
// compilers can assume UB never happens and optimize AGGRESSIVELY around it

// signed integer overflow (wraps in most cases but NOT guaranteed)
int x = INT_MAX;
x + 1;   // UB. could be anything.

// accessing array out of bounds
int arr[5];
arr[10] = 42;  // writing to random memory. corrupts stuff silently.

// dereferencing NULL or freed pointer
int* p = NULL;
*p = 5;   // segfault. program dead.

// use-after-free
free(p);
*p = 5;   // UB. you freed it! memory belongs to OS now.

// double-free
free(p);
free(p);  // UB. could corrupt malloc's internal structures.

// data races (in multithreaded code)

// USE TOOLS: -fsanitize=address,undefined when compiling to catch this stuff
// gcc -fsanitize=address,undefined -g your_file.c -o out
Undefined behavior is C's way of saying "your problem now." Use sanitizers (-fsanitize=address,undefined) to find UB before it finds you.
volatile, restrict & inline — Telling the Compiler Extra Stuff
// volatile — tells compiler "don't optimize this. it can change externally."
// used for hardware registers, shared memory, signal handlers
volatile int sensorVal;   // compiler won't cache this in a register
volatile int* reg = (int*)0xDEADBEEF;  // memory-mapped hardware register

// without volatile, the compiler might optimize this loop away thinking
// the value never changes (because it doesn't change in THIS code)
while (sensorVal == 0) {}   // waits for hardware to set sensorVal != 0

// restrict — pointer is the ONLY way to access this memory
// enables compiler optimizations (no aliasing assumptions needed)
// common in high-perf code like memcpy implementations
void fastCopy(int restrict *dst, const int restrict *src, int n) {
    for (int i = 0; i < n; i++) dst[i] = src[i];
}

// inline — suggests compiler paste the function body in place of the call
// eliminates function call overhead. good for tiny hot functions.
static inline int clamp(int val, int lo, int hi) {
    return val < lo ? lo : (val > hi ? hi : val);
}
setjmp / longjmp — Poor Man's Exception Handling
#include <setjmp.h>

// setjmp/longjmp = non-local goto. like try/catch but cursed.
// setjmp saves the CPU state. longjmp teleports back to that state.
// used in interpreters, error recovery, and deeply nested escape patterns.

jmp_buf env;   // stores the CPU state (registers, stack pointer, etc)

void riskyFunction() {
    printf("About to do something dumb...\n");
    longjmp(env, 1);   // teleport back to where setjmp was called, return value 1
    printf("This line never executes\n");
}

int main() {
    int val = setjmp(env);   // returns 0 first time. after longjmp, returns whatever you passed.

    if (val == 0) {
        printf("Normal execution\n");
        riskyFunction();
    } else {
        printf("Recovered! longjmp returned %d\n", val);  // prints this
    }
    return 0;
}

// WARNING: destructors, VLAs, and local vars modified between setjmp/longjmp
// can have undefined behavior. this is powerful but genuinely spooky to use.
Use sparingly. longjmp skips destructors and cleanup code. If you do use it, make sure you know exactly what state you're jumping back to.
Advanced Struct Tricks — Bit Fields, Unions, Flexible Arrays
// BIT FIELDS — pack multiple small values into one int
// saves memory. used in protocols, hardware, OS kernels
struct Flags {
    unsigned int isActive  : 1;   // only uses 1 bit
    unsigned int isAdmin   : 1;   // 1 bit
    unsigned int priority  : 4;   // 4 bits (values 0-15)
    unsigned int padding   : 2;   // explicit padding to byte-align
};  // whole thing fits in 1 byte instead of 12!

// UNION — multiple fields sharing the SAME memory
// only ONE field is valid at a time. size = size of largest member
union Data {
    int   i;    // 4 bytes
    float f;    // 4 bytes — same 4 bytes as i!
    char  c[4]; // 4 bytes — same 4 bytes! overlapping storage
};
// writing to .f and then reading .i = type punning. spooky but used in serialization

// FLEXIBLE ARRAY MEMBER (C99+) — struct with variable-length tail
typedef struct {
    int   count;
    int   data[];  // flexible array — size determined at allocation time
} Buffer;

Buffer* makeBuffer(int n) {
    return malloc(sizeof(Buffer) + n * sizeof(int));  // allocate header + n ints
}
Generic Programming in C — void* and Macros to the Rescue
// C has no templates. but void* lets you write type-agnostic functions.
// qsort from stdlib.h uses this — it sorts ANYTHING
#include <stdlib.h>

// comparison function: returns neg, 0, or pos (like strcmp)
int compareInts(const void* a, const void* b) {
    return (*(int*)a) - (*(int*)b);
    // cast void* to int*, then dereference. feels gross but it works.
}

int arr[] = {5, 2, 8, 1, 9};
qsort(arr, 5, sizeof(int), compareInts);  // sorts ascending
// now arr = {1, 2, 5, 8, 9}

// X-Macro trick — generate repetitive code from a list
// define the list once, use it in multiple contexts
#define COLORS  \
    X(RED,   0) \
    X(GREEN, 1) \
    X(BLUE,  2)

// generate the enum from the list
typedef enum {
#define X(name, val) name = val,
    COLORS
#undef X
} Color;

// generate string table from the SAME list
const char* colorNames[] = {
#define X(name, val) [val] = #name,
    COLORS
#undef X
};
6
NASA Rules & Production-Grade C
The Power of 10. Safety-critical. This code runs on spacecraft.
The Power of 10 — NASA/JPL Rules for Safety-Critical C Code
What is this? The "Power of 10" is a set of 10 strict coding rules developed by Gerard Holzmann at NASA/JPL for writing C code in safety-critical aerospace systems (rovers, satellites, spacecraft software). If your code runs on a rocket and it crashes, you can't push a hotfix.
/* ══════════════════════════════════════════════════════════════
   RULE 1 — No complex flow constructs (goto, setjmp, longjmp)
   Simple control flow = auditable code. goto is literally banned.
   Every function should have a single, obvious execution path.
   ══════════════════════════════════════════════════════════════ */

// BANNED — don't even think about it
goto cleanup;

// Use structured cleanup instead
int process(int n) {
    int *buf = malloc(n * sizeof(int));
    if (buf == NULL) return -1;   // early return = structured, readable
    // ... do stuff ...
    free(buf);
    return 0;
}

/* ══════════════════════════════════════════════════════════════
   RULE 2 — All loops must have a fixed upper bound
   An infinite loop is a potential system hang. Unacceptable.
   If the verifier can't prove the loop terminates, it's banned.
   ══════════════════════════════════════════════════════════════ */

#define MAX_ITER 1000   // explicit upper bound. always.

// BANNED — while(1) with no guaranteed exit
while (1) { /* could run forever */ }

// Bounded loop with safety counter
int iter = 0;
while (condition && iter < MAX_ITER) {
    // ... do work ...
    iter++;
}
// even if condition never flips, we exit after MAX_ITER. guaranteed.
if (iter >= MAX_ITER) {
    logError("Max iterations exceeded — something is very wrong");
}

/* ══════════════════════════════════════════════════════════════
   RULE 3 — No dynamic memory allocation after initialization
   malloc at runtime = unpredictable timing, fragmentation, OOM.
   In hard real-time systems, you allocate everything up front.
   ══════════════════════════════════════════════════════════════ */

// BANNED in flight software — malloc during mission
void handle_event() {
    int* buf = malloc(100);  // NOPE. timing is nondeterministic.
}

// Pre-allocated static pool — allocate ONCE at startup
#define POOL_SIZE 1024
static uint8_t memPool[POOL_SIZE];   // fixed-size memory arena
static size_t  poolOffset = 0;

void* poolAlloc(size_t size) {
    if (poolOffset + size > POOL_SIZE) return NULL;  // out of pool
    void* ptr = memPool + poolOffset;
    poolOffset += size;
    return ptr;
}

/* ══════════════════════════════════════════════════════════════
   RULE 4 — No function longer than 60 lines (1 screen)
   If you can't see the whole function at once, it's too complex.
   Break it up. Each function = one well-defined task. Period.
   ══════════════════════════════════════════════════════════════ */

// A 300-line function with nested ifs 5 deep = instant reject at code review

// Small, focused functions. each does ONE thing and does it well.
static bool validateInput(int val) { return val >= 0 && val < 100; }
static int  processVal(int val)     { return val * 2 + 1; }
static void outputResult(int res)  { printf("%d\n", res); }

void doWork(int input) {
    if (!validateInput(input)) return;
    int result = processVal(input);
    outputResult(result);
}

/* ══════════════════════════════════════════════════════════════
   RULE 5 — Minimum 2 assertions per function
   assert() checks invariants. if they fire = bug caught early.
   Assertions are the "sanity checks that you thought were obvious."
   ══════════════════════════════════════════════════════════════ */

#include <assert.h>
#include <stdint.h>

int32_t divide(int32_t num, int32_t den) {
    assert(den != 0);          // precondition: can't divide by zero
    assert(num != INT32_MIN || den != -1);  // INT32_MIN / -1 overflows
    int32_t result = num / den;
    // postcondition: verify result is in expected range
    assert(result * den == num || 1);  // sanity check
    return result;
}

// for production flight software, use custom assert that logs and recovers:
#define ASSERT(cond, msg) \
    do { if (!(cond)) { log_critical(msg); safe_shutdown(); } } while(0)

/* ══════════════════════════════════════════════════════════════
   RULE 6 — Declare variables at smallest possible scope
   Keep variables close to where they're used. Less surface area
   for bugs. Easier to reason about. Easier to audit.
   ══════════════════════════════════════════════════════════════ */

// Old C89 style — all vars at top of function (don't do this)
void old_style() {
    int i, j, sum, count, tmp;  // what are these even for? who knows anymore.
    // ... 100 lines later ...
    sum = 0;
}

// C99 style — declare where you need it
void new_style() {
    for (int i = 0; i < 10; i++) {  // i only exists inside this for loop
        int doubled = i * 2;          // doubled only exists here
        printf("%d\n", doubled);
    }
}

/* ══════════════════════════════════════════════════════════════
   RULE 7 — Check return values of ALL non-void functions
   If a function can fail, assume it WILL fail eventually.
   Ignoring return values = ignoring errors = mystery crashes later.
   ══════════════════════════════════════════════════════════════ */

// BANNED — ignoring return values
fclose(file);           // fclose can fail (flush error). you don't care?
malloc(1000);           // allocated memory and threw the pointer away lol
scanf("%d", &x);        // what if scanf failed? x has garbage value.

// Check everything. everything.
if (fclose(file) != 0) {
    perror("fclose failed");
    // handle appropriately
}

int* buf = malloc(1000);
if (buf == NULL) { return ERR_NOMEM; }

int n = scanf("%d", &x);
if (n != 1) { return ERR_BADINPUT; }  // scanf returns # items read

// use GCC's __attribute__((warn_unused_result)) to enforce this in your APIs
__attribute__((warn_unused_result))
int criticalOp(void) { return -1; }  // compiler warns if caller ignores return

/* ══════════════════════════════════════════════════════════════
   RULE 8 — Limit preprocessor use. No macros for functions.
   Macros have no type checking, no scope, expand unexpectedly.
   Use inline functions or const variables instead where possible.
   ══════════════════════════════════════════════════════════════ */

// Macro function — dangerous, no type check, double-evaluation
#define MAX(a,b)  ((a) > (b) ? (a) : (b))
MAX(x++, y)  // x++ gets evaluated TWICE. classic macro footgun.

// inline function — type-safe, debuggable, no double-eval
static inline int max_int(int a, int b) { return a > b ? a : b; }

// const instead of #define for constants
static const int MAX_BUFFER = 4096;  // has type, has scope, shows in debugger
#define MAX_BUFFER_BAD 4096           // invisible in debugger, no type check

/* ══════════════════════════════════════════════════════════════
   RULE 9 — Limit pointer use. No function pointers. No >1 level of pointer dereferencing.
   Pointers are error-prone. Deeply nested pointer chains = auditing nightmare.
   ══════════════════════════════════════════════════════════════ */

// too many levels of indirection
int*** triplePtr;   // what the hell even is this. who hurt you.

// function pointers in flight code (makes static analysis hard)
void (*handler)(int);  // banned — static analyzer can't trace this

// max one level in regular code
int* ptr = &x;   // fine
int** pptr;        // only when absolutely necessary (e.g., passing pointer to pointer)

// for struct members, a single level is fine
Person* p = &person;
p->age = 17;  // one dereference. clear. auditable.

/* ══════════════════════════════════════════════════════════════
   RULE 10 — Compile with all warnings enabled. Zero warnings.
   Warnings are the compiler saying "this looks sketchy."
   In safety-critical code, warnings are ERRORS. All of them.
   ══════════════════════════════════════════════════════════════ */

// The full compile flags you should use for any serious C project:
// gcc -std=c11                         — use C11 standard
//     -Wall -Wextra -Wpedantic         — enable all the warnings
//     -Werror                          — treat warnings as errors (zero tolerance)
//     -Wshadow                         — warn about variable shadowing
//     -Wconversion                     — warn about implicit type conversions
//     -Wformat=2                       — extra format string checks
//     -fstack-protector-strong         — detect stack smashing at runtime
//     -D_FORTIFY_SOURCE=2              — harden string/memory operations
//     -fsanitize=address,undefined     — catch memory bugs + UB during testing
//     -fanalyzer                       — GCC's static analysis pass
//
// Short version: gcc -std=c11 -Wall -Wextra -Werror -Wpedantic -g file.c -o out
//
// Also run these tools regularly:
// - valgrind ./out         — memory leak detection
// - clang-tidy             — static analysis
// - cppcheck               — another static analyzer (catches different stuff)
// - splint                 — annotation-based analysis (used at JPL)
The real NASA mindset: Every time you write code, ask yourself — "if this ran for 10 years unattended on a spacecraft 500 million km away, would it still be correct?" That's the bar. Defensive programming, explicit error handling, and static analysis aren't optional. They're how software gets to Mars.
Quick Reference — Format Specifiers & Escape Sequences
printf / scanf Format Specifiers
SpecifierTypeExample
%d / %iint (signed decimal)printf("%d", 42) → "42"
%uunsigned intprintf("%u", 42u)
%ldlong intprintf("%ld", 1234567L)
%lldlong long intprintf("%lld", big)
%ffloat/doubleprintf("%.2f", 3.14) → "3.14"
%escientific notationprintf("%e", 12345.0) → "1.23e+04"
%ccharprintf("%c", 'A') → "A"
%sstring (char*)printf("%s", "yo")
%ppointer addressprintf("%p", ptr)
%x / %Xhex (lower/upper)printf("%x", 255) → "ff"
%ooctalprintf("%o", 8) → "10"
%zusize_tprintf("%zu", sizeof(x))
%%literal %printf("100%%")
Escape Sequences & Common Headers
EscapeMeaning
\nNewline (enter key)
\tTab
\rCarriage return
\\Literal backslash
\"Literal double quote inside string
\'Literal single quote
\0Null character (string terminator)
\aBell (plays beep sound lol)

HeaderWhat it gives you
<stdio.h>printf, scanf, FILE, fopen, fclose
<stdlib.h>malloc, free, rand, exit, atoi
<string.h>strlen, strcpy, strcmp, memcpy
<math.h>sqrt, pow, sin, cos, fabs
<stdbool.h>bool, true, false (C99+)
<stdint.h>int8_t, uint32_t, int64_t etc.
<limits.h>INT_MAX, INT_MIN, CHAR_MAX
<assert.h>assert()
<errno.h>errno, EINVAL, ENOMEM etc.
<time.h>time(), clock(), struct tm
<pthread.h>threads, mutexes (POSIX)