C Programming Cheat Sheet

This C cheat sheet covers 30 sections: types and limits, operators and precedence, pointers, arrays and strings, malloc and free, structs and unions, the preprocessor, printf and scanf, files, the standard library, C23 features, threads and atomics, bit tricks, gcc flags, sanitizers, undefined behavior and the compiler errors you will actually meet. Search it, filter it by level or by C standard, copy any line with one click, and explain declarations, check printf formats or lint your code without leaving the page.

30sections
209snippets
C99 to C23standards covered
7C tools

Updated September 29, 2026, checked against the C23 standard, GCC 16.2 and Clang 23.1. Print it for a PDF copy.

/
level

return 0;

Nothing matches that search. Try a shorter keyword such as malloc, printf or struct. Several words are combined, so every one of them has to match. Keys: / focuses the search, Esc clears it, t switches the theme.

01

Compile & Run

compilers, the build pipeline, a C toolbox and builders

C Toolbox: Lint, Explain Declarations, Check printf, Precedence, Struct Layout

core
Ctrl+Enter lints. Explain declaration reads declarations such as int (*(*fp)(char))[10] in plain English. Check printf lists what each conversion expects and counts the arguments. Show precedence parenthesizes one expression per line. Struct layout shows offsets, padding and a smaller member order. Number and bits takes 255 0xFF 0b1010 -1 3.14 'A'. Run on godbolt opens the program in Compiler Explorer in a new tab; nothing is sent until you press it.
output

          
Paste C code, a declaration, a printf call, a struct or an expression above, then pick an action.

Builders: Compiler Flags, Makefile, CMake, Integer Types, Debugging

everyday
command

          

Compile and Run

core

Hello, world, compiled and run

#include <stdio.h>

int main(void) {
    puts("Hello, world");
    return 0;
}

The commands

gcc -std=c23 -Wall -Wextra -g main.c -o hello
./hello
clang -std=c23 -Wall -Wextra -g main.c -o hello
gcc --version

Visual Studio command line

cl /std:clatest /W4 main.c
main.exe

Try code without installing anything

# Compiler Explorer (godbolt.org): pick "C" and a compiler, see the assembly and run the program
# a container with GCC:
docker run --rm -v "$PWD":/src -w /src gcc:15 gcc -std=c23 -Wall main.c -o main

The Build Pipeline

everyday

Stop after each stage

gcc -E main.c -o main.i          # preprocess only: #include and #define expanded
gcc -S main.c -o main.s          # compile to assembly
gcc -c main.c -o main.o          # assemble to an object file
gcc main.o util.o -o app -lm     # link objects and libraries

Multiple files and headers

// util.h
#ifndef UTIL_H
#define UTIL_H
int add(int a, int b);
#endif

// util.c
#include "util.h"
int add(int a, int b) { return a + b; }

// main.c
#include <stdio.h>
#include "util.h"
int main(void) { printf("%d\n", add(2, 3)); }

Libraries: order matters

gcc main.o -o app -L./lib -lfoo -lm
gcc main.o -o app $(pkg-config --cflags --libs libcurl)
ar rcs libutil.a util.o str.o          # a static library
gcc -shared -fPIC util.c -o libutil.so # a shared library
02

Program Structure

main, headers, comments, identifiers, exit codes and command line arguments

main and Arguments

core

argc and argv

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("%d arguments\n", argc);
    for (int i = 0; i < argc; i++)
        printf("argv[%d] = %s\n", i, argv[i]);
    return 0;
}

Exit codes

#include <stdlib.h>

int main(void) {
    if (!init()) return EXIT_FAILURE;
    atexit(cleanup);          // runs on return from main or exit()
    exit(EXIT_SUCCESS);
}

Syntax Basics

core

Comments, statements and blocks

/* a block comment,
   over several lines */
// a line comment (C99+)
int total = 0;               // a declaration with an initializer
{                            // a block opens a new scope
    int total = 1;           // shadows the outer total
}

Keywords of C23

GroupKeywords
Typesvoid char short int long float double signed unsigned bool _BitInt _Complex
Type buildingstruct union enum typedef typeof typeof_unqual
Qualifiersconst volatile restrict _Atomic
Storageauto extern static register thread_local constexpr
Controlif else switch case default for while do break continue goto return
Othersizeof alignof alignas static_assert inline nullptr true false _Generic _Noreturn

Identifiers you must not use

int _Count;          // reserved: underscore followed by a capital letter
int __total;         // reserved: two underscores
int str_len;         // fine; but names starting with str, mem or is + lower case letter are reserved for <string.h> and <ctype.h>

Headers You Will Use

core

The standard headers

HeaderFor
<stdio.h>printf, scanf, files
<stdlib.h>malloc, free, exit, strtol, qsort, rand
<string.h>strlen, strcpy, memcpy, memset, strcmp
<stdint.h> <inttypes.h>int32_t, uint64_t and their printf macros
<stdbool.h>bool, true, false (built in since C23)
<stddef.h>size_t, ptrdiff_t, NULL, offsetof
<limits.h> <float.h>INT_MAX, CHAR_BIT, DBL_EPSILON
<ctype.h>isalpha, isdigit, toupper
<math.h>sqrt, pow, sin, floor (link with -lm on Linux)
<errno.h>errno and error codes
<assert.h>assert, static_assert
<time.h>time, clock, strftime
<threads.h> <stdatomic.h>C11 threads and atomics
<stdckdint.h> <stdbit.h>C23 checked arithmetic and bit utilities

Reading Input Arguments

everyday

Parse numeric arguments safely

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(void) {
    const char *argv1 = "250";          // stands in for argv[1]
    char *end;
    errno = 0;
    long n = strtol(argv1, &end, 10);
    if (errno || *end != '\0' || n < 1 || n > 1000) {
        fprintf(stderr, "count must be 1 to 1000\n");
        return EXIT_FAILURE;
    }
    printf("processing %ld items\n", n);
    return EXIT_SUCCESS;
}
03

Types & Limits

integer and floating types, sizes, limits, literals, conversions and fixed width types

Sizes on Real Platforms

core

sizeof in bytes for the common data models

TypeMinimum by the standardLP64 (Linux, macOS)LLP64 (Windows x64)ILP32 (32-bit)
char1111
short2222
int2444
long4844
long long8888
void *, size_tnone884
float / double4 / 8 in practice4 / 84 / 84 / 8
long doubleat least double16 (x86-64), 16 (ARM64 Linux), 8 (macOS ARM64)812 (x86)

Print sizes and limits

#include <stdio.h>
#include <limits.h>
#include <stdint.h>

int main(void) {
    printf("int %zu, long %zu, pointer %zu\n", sizeof(int), sizeof(long), sizeof(void *));
    printf("INT_MAX %d, LONG_MAX %ld\n", INT_MAX, LONG_MAX);
    printf("UINT64_MAX %llu, CHAR_BIT %d\n", (unsigned long long)UINT64_MAX, CHAR_BIT);
}

Literals

core

Integer and floating literals

int d = 42, o = 052, h = 0x2A, b = 0b101010;   // binary literals are C23
long big = 1'000'000L;                          // digit separators are C23
unsigned u = 42u;
unsigned long long ull = 42ULL;
float f = 1.5f;
double x = 1.5e-3;
long double ld = 1.5L;
char c = 'A', nl = '\n';

Character and string literals

char a = 'A';                // an int value of type char
char s[] = "hi";             // 3 bytes: 'h', 'i', '\0'
wchar_t w[] = L"hi";
char8_t u8s[] = u8"héllo";   // UTF-8, char8_t is C23
char16_t u16 = u'é';
char32_t u32 = U'🙂';

Escape sequences

EscapeMeaning
\n \t \rnewline, tab, carriage return
\\ \' \"backslash, quote, double quote
\0the null character, the end of every string
\xhha byte in hex
\oooa byte in octal
€ \U0001F642a Unicode character

Conversions

everyday

Integer promotion and the usual conversions

#include <stdio.h>

int main(void) {
    unsigned int u = 1;
    int i = -1;
    printf("%d\n", i < u);        // -1 is converted to unsigned
    char c = 200;                  // implementation defined: char is signed on x86
    printf("%d\n", c);
    printf("%d\n", 7 / 2);         // integer division truncates
    printf("%.2f\n", 7 / 2.0);
}

Explicit casts

double avg = (double)sum / count;          // cast before dividing
int rounded = (int)(x + 0.5);              // truncates toward zero
unsigned char byte = (unsigned char)value; // keeps the low 8 bits

Arbitrary width integers

#include <stdio.h>

int main(void) {
    unsigned _BitInt(128) big = (unsigned _BitInt(128))1 << 100;
    unsigned _BitInt(12) small = 4095;
    small += 1;                                // wraps to 0
    printf("%d %d\n", (int)(big >> 100), (int)small);
}

Floating Point

everyday

Precision and comparison

#include <stdio.h>
#include <math.h>
#include <float.h>

int main(void) {
    double a = 0.1 + 0.2;
    printf("%.17f\n", a);
    printf("%d\n", a == 0.3);
    printf("%d\n", fabs(a - 0.3) < 1e-9);
    printf("%d %d\n", FLT_DIG, DBL_DIG);
}
04

Operators & Precedence

arithmetic, comparison, logical, bitwise, assignment and the precedence table

The Operators

core

Arithmetic and comparison

a + b   a - b   a * b   a / b   a % b      // % needs integers; sign follows the left operand
a == b  a != b  a < b   a <= b  a > b  a >= b
!a      a && b  a || b                     // && and || stop early (short circuit)
cond ? x : y

Increment, assignment, comma

i++;  ++i;  i--;  --i;
x += 5;  x -= 1;  x *= 2;  x /= 3;  x %= 4;
x <<= 1; x >>= 1; x &= m; x |= m; x ^= m;
for (int i = 0, j = n - 1; i < j; i++, j--)   // the comma operator

Precedence, highest first

LevelOperatorsAssociativity
1() [] . -> postfix ++ --left to right
2prefix ++ --, unary + - ! ~, * &, casts, sizeof, alignofright to left
3* / %left to right
4+ -left to right
5<< >>left to right
6< <= > >=left to right
7== !=left to right
8&left to right
9^left to right
10vertical bar, bitwise ORleft to right
11&&left to right
12double vertical bar, logical ORleft to right
13?:right to left
14= += -= and the other assignmentsright to left
15,left to right

Traps

everyday

Bitwise operators bind weaker than comparisons

if (flags & MASK == 0)     // means flags & (MASK == 0)
if ((flags & MASK) == 0)   // what you meant
x = a << 2 + 1;            // a << 3

Order of evaluation is not left to right

printf("%d %d\n", i++, i++);     // undefined behavior: i modified twice
a[i] = i++;                      // undefined behavior
f(g(), h());                     // g and h run in an unspecified order

Integer division and remainder of negatives

#include <stdio.h>

int main(void) {
    printf("%d %d\n", -7 / 2, -7 % 2);
    printf("%d %d\n", 7 / -2, 7 % -2);
}

Short Circuit and the Conditional

everyday

&& and || skip the right side

#include <stdio.h>

static int calls = 0;
static int check(int v) { calls++; return v; }

int main(void) {
    int *p = NULL;
    if (p && *p > 0) puts("positive");      // *p is never read when p is NULL
    int r = check(0) && check(1);
    int s = check(1) || check(1);
    printf("%d %d calls=%d\n", r, s, calls);
    int max = 3 > 7 ? 3 : 7;
    printf("%d\n", max);
}
05

Control Flow

if, switch, loops, break, continue, goto and C23 fallthrough

Conditions

core

if, else if, else

if (score >= 90) {
    grade = 'A';
} else if (score >= 80) {
    grade = 'B';
} else {
    grade = 'C';
}

switch

switch (cmd) {
case 'q':
    quit();
    break;
case 'h':
case '?':                  // several labels share one body
    help();
    break;
case 'v':
    verbose = true;
    [[fallthrough]];       // intended, silences -Wimplicit-fallthrough (C23)
default:
    run(cmd);
}

Loops

core

for, while, do while

for (int i = 0; i < n; i++) { sum += a[i]; }
while (fgets(line, sizeof line, fp)) { parse(line); }
do {
    c = getchar();
} while (c != '\n' && c != EOF);
for (;;) { if (done()) break; }     // an infinite loop

break and continue

#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 10; i++) {
        if (i % 2 == 0) continue;   // skip even numbers
        if (i > 7) break;           // stop the loop
        printf("%d ", i);
    }
    putchar('\n');
}

Leave nested loops

for (int i = 0; i < rows; i++)
    for (int j = 0; j < cols; j++)
        if (grid[i][j] == target) goto found;
puts("not found");
return;
found:
printf("at %d,%d\n", i, j);

One cleanup path with goto

int load(const char *path) {
    int rc = -1;
    FILE *f = fopen(path, "rb");
    if (!f) goto out;
    char *buf = malloc(4096);
    if (!buf) goto close_file;
    if (fread(buf, 1, 4096, f) == 0) goto free_buf;
    rc = 0;
free_buf:
    free(buf);
close_file:
    fclose(f);
06

Arrays

declaring, initializing, sizing, passing and multi dimensional arrays

Declare and Initialize

core

Initializers

int a[5];                         // uninitialized inside a function
int b[5] = {1, 2, 3};             // the rest are 0
int c[] = {10, 20, 30};           // size 3, counted by the compiler
int d[5] = {};                    // all zero; the empty initializer is C23, {0} works everywhere
int e[10] = {[2] = 7, [9] = 1};   // designated initializers (C99)
char s[] = "abc";                 // 4 bytes including the '\0'

Number of elements

#include <stdio.h>

int main(void) {
    int scores[] = {90, 72, 88, 65};
    size_t n = sizeof scores / sizeof scores[0];
    printf("%zu elements\n", n);
    for (size_t i = 0; i < n; i++) printf("%d ", scores[i]);
    putchar('\n');
}

Arrays and Functions

core

An array parameter is a pointer

#include <stdio.h>

void show(int arr[], size_t n) {           // same as int *arr
    printf("inside: %zu\n", sizeof arr);   // the size of a pointer
    for (size_t i = 0; i < n; i++) printf("%d ", arr[i]);
    putchar('\n');
}

int main(void) {
    int v[] = {1, 2, 3, 4, 5};
    printf("outside: %zu\n", sizeof v);
    show(v, sizeof v / sizeof v[0]);
}

Document the minimum size in the parameter

void fill(size_t n, int buf[static n]);   // buf has at least n elements, never null
void sum3(const double v[static 3]);

Multi-dimensional Arrays

everyday

2D arrays are arrays of arrays

#include <stdio.h>

int main(void) {
    int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};
    for (int r = 0; r < 2; r++) {
        for (int c = 0; c < 3; c++) printf("%d ", grid[r][c]);
        putchar('\n');
    }
    printf("%zu bytes, row of %zu\n", sizeof grid, sizeof grid[0]);
}

Pass a 2D array with its sizes

void print_matrix(size_t rows, size_t cols, int m[rows][cols]) {
    for (size_t r = 0; r < rows; r++)
        for (size_t c = 0; c < cols; c++)
            printf("%d%c", m[r][c], c + 1 == cols ? '\n' : ' ');
}

Variable length arrays

void work(size_t n) {
    double tmp[n];              // on the stack, size decided at run time
    for (size_t i = 0; i < n; i++) tmp[i] = 0.0;
}

Common Array Tasks

everyday

Reverse, find the maximum, copy

#include <stdio.h>
#include <string.h>

int main(void) {
    int a[] = {3, 9, 2, 7, 5};
    size_t n = sizeof a / sizeof a[0];
    for (size_t i = 0, j = n - 1; i < j; i++, j--) { int t = a[i]; a[i] = a[j]; a[j] = t; }
    int max = a[0];
    for (size_t i = 1; i < n; i++) if (a[i] > max) max = a[i];
    int copy[5];
    memcpy(copy, a, sizeof a);
    for (size_t i = 0; i < n; i++) printf("%d ", copy[i]);
    printf("max=%d\n", max);
}
07

Strings

char arrays, string.h, copying safely, comparing, searching and splitting

Basics

core

A string is a char array ending in '\0'

#include <stdio.h>
#include <string.h>

int main(void) {
    char word[] = "hello";
    printf("%zu %zu\n", strlen(word), sizeof word);
    word[0] = 'H';
    printf("%s\n", word);
    const char *lit = "fixed";      // points to read only memory
    printf("%c\n", lit[1]);
}

Copy and join safely with snprintf

char path[64];
snprintf(path, sizeof path, "%s/%s.txt", dir, name);   // always terminated, never overflows
int need = snprintf(NULL, 0, "%s/%s.txt", dir, name);   // how many bytes it would take
if (need >= (int)sizeof path) { /* the result was truncated */ }

Compare

if (strcmp(a, b) == 0)        { /* equal */ }
if (strncmp(line, "GET ", 4) == 0) { /* starts with GET */ }
if (strcasecmp(a, b) == 0)    { /* equal ignoring case, POSIX */ }

Searching and Splitting

everyday

Search

#include <stdio.h>
#include <string.h>

int main(void) {
    const char *s = "key=value; other=1";
    const char *eq = strchr(s, '=');
    const char *semi = strstr(s, "; ");
    printf("%td %td\n", eq - s, semi - s);
    printf("%zu\n", strspn("12345abc", "0123456789"));
    printf("%.*s\n", (int)(eq - s), s);
}

Split into tokens

#include <stdio.h>
#include <string.h>

int main(void) {
    char csv[] = "red,green,,blue";
    for (char *tok = strtok(csv, ","); tok; tok = strtok(NULL, ","))
        printf("[%s]\n", tok);
}

Duplicate a string

char *copy = strdup(src);          // malloc + copy; standard since C23, POSIX before
char *first5 = strndup(src, 5);    // at most 5 characters, always terminated
free(copy); free(first5);

Character classes

#include <ctype.h>
if (isdigit((unsigned char)c)) { }
if (isspace((unsigned char)c)) { }
c = (char)toupper((unsigned char)c);

Memory Functions

everyday

memcpy, memmove, memset, memcmp

memcpy(dst, src, n);              // the ranges must not overlap
memmove(dst, dst + 1, n - 1);     // overlapping ranges are fine
memset(buf, 0, sizeof buf);       // fill bytes
if (memcmp(a, b, n) == 0) { }     // compare bytes

Build a string of unknown length

char *s = NULL;
size_t len = 0;
FILE *m = open_memstream(&s, &len);      // POSIX
fprintf(m, "%d items, total %.2f\n", n, total);
fclose(m);                               // s now holds the text
free(s);

String Tasks

everyday

Trim, lower case, and count words

#include <stdio.h>
#include <ctype.h>
#include <string.h>

static char *trim(char *s) {
    while (isspace((unsigned char)*s)) s++;
    char *end = s + strlen(s);
    while (end > s && isspace((unsigned char)end[-1])) end--;
    *end = '\0';
    return s;
}

int main(void) {
    char text[] = "   Hello Big WORLD  ";
    char *t = trim(text);
    for (char *p = t; *p; p++) *p = (char)tolower((unsigned char)*p);
    int words = 0, in = 0;
    for (char *p = t; *p; p++) { if (isspace((unsigned char)*p)) in = 0; else if (!in) { in = 1; words++; } }
    printf("[%s] %d words\n", t, words);
}
08

Pointers

addresses, dereferencing, pointer arithmetic, void pointers, pointers to pointers and null

Basics

core

Address of, dereference

#include <stdio.h>

int main(void) {
    int x = 10;
    int *p = &x;          // p holds the address of x
    *p = 20;              // write through the pointer
    printf("%d %d\n", x, *p);
    printf("%d\n", p == &x);
}

Null pointers

int *p = NULL;           // or nullptr in C23
if (p != NULL) { use(*p); }
if (!p) { /* p is null */ }
free(p);                 // free(NULL) is a no-op

Pass by pointer to change the caller's variable

#include <stdio.h>

void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }

int main(void) {
    int x = 1, y = 2;
    swap(&x, &y);
    printf("%d %d\n", x, y);
}

Pointer Arithmetic

everyday

Moving through an array

#include <stdio.h>
#include <stddef.h>

int main(void) {
    int a[] = {10, 20, 30, 40};
    int *p = a;             // same as &a[0]
    printf("%d %d %d\n", *p, *(p + 2), p[3]);
    int *end = a + 4;       // one past the end: valid to form, not to read
    ptrdiff_t n = end - p;
    printf("%td\n", n);
    for (int *q = a; q < end; q++) printf("%d ", *q);
    putchar('\n');
}

void pointers

void *any = &x;               // any object pointer converts to void * and back
int *ip = any;                // no cast needed in C
size_t n = 10;
void *mem = malloc(n);
unsigned char *bytes = mem;   // look at the raw bytes

Pointers to pointers

#include <stdio.h>
#include <stdlib.h>

void alloc_int(int **out, int v) {
    *out = malloc(sizeof **out);
    if (*out) **out = v;
}

int main(void) {
    int *p = NULL;
    alloc_int(&p, 42);
    printf("%d\n", *p);
    free(p);
}

const and Pointers

everyday

Read declarations right to left

const int *p;          // pointer to const int: *p = 1 is an error, p = &y is fine
int *const q = &x;     // const pointer to int: *q = 1 is fine, q = &y is an error
const int *const r;    // both
const char **argvp;    // pointer to pointer to const char

Dangling pointers

int *bad(void) {
    int local = 5;
    return &local;             // local dies when the function returns
}
char *s = malloc(8);
free(s);
s[0] = 'x';                    // use after free

Pointers and Arrays of Strings

everyday

An array of pointers to strings

#include <stdio.h>

int main(void) {
    const char *days[] = {"Mon", "Tue", "Wed"};
    const char **p = days;
    printf("%s %c %zu\n", p[1], *days[2], sizeof days / sizeof days[0]);
    for (const char **d = days; d < days + 3; d++) printf("%s ", *d);
    putchar('\n');
}
09

Dynamic Memory

malloc, calloc, realloc, free, ownership and leak free patterns

Allocate and Free

core

malloc and free

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    size_t n = 5;
    int *a = malloc(n * sizeof *a);
    if (!a) { perror("malloc"); return 1; }
    for (size_t i = 0; i < n; i++) a[i] = (int)(i * i);
    for (size_t i = 0; i < n; i++) printf("%d ", a[i]);
    putchar('\n');
    free(a);
    a = NULL;
}

calloc zeroes and checks the multiplication

int *counts = calloc(n, sizeof *counts);     // all zero, and n * size cannot silently overflow
struct node *nodes = calloc(count, sizeof *nodes);

realloc: grow without leaking

int *tmp = realloc(a, new_n * sizeof *a);
if (!tmp) { free(a); return -1; }      // a is still valid when realloc fails
a = tmp;

Patterns

everyday

A growable array

#include <stdio.h>
#include <stdlib.h>

typedef struct { int *data; size_t len, cap; } IntVec;

int push(IntVec *v, int x) {
    if (v->len == v->cap) {
        size_t cap = v->cap ? v->cap * 2 : 8;
        int *d = realloc(v->data, cap * sizeof *d);
        if (!d) return -1;
        v->data = d; v->cap = cap;
    }
    v->data[v->len++] = x;
    return 0;
}

int main(void) {
    IntVec v = {0};
    for (int i = 0; i < 20; i++) push(&v, i);
    printf("%zu items, capacity %zu\n", v.len, v.cap);
    free(v.data);
}

Who frees what

char *make_greeting(const char *name);    // returns memory the caller must free
void set_name(struct user *u, const char *name);  // copies name; u owns the copy
const char *user_name(const struct user *u);      // borrowed: do not free

Aligned and sized allocation

void *buf = aligned_alloc(64, 4096);   // C11: alignment 64, size a multiple of it
free(buf);
void *p = malloc(100);
free_sized(p, 100);                    // C23: tell the allocator the size

Stack or Heap

everyday

Where each object lives

Declared asLives inLifetime
local variable, VLAstackuntil the block ends
static local, globalstatic storagethe whole program
malloc, calloc, reallocheapuntil free
string literalread only static storagethe whole program
compound literal in a functionstackuntil the block ends

Debugging Allocations

everyday

A leak, found by AddressSanitizer

#include <stdlib.h>
#include <string.h>

char *copy_name(const char *s) { char *c = malloc(strlen(s) + 1); strcpy(c, s); return c; }

int main(void) {
    char *a = copy_name("Ana");
    a = copy_name("Radu");        // the first copy is lost
    free(a);
}
10

Structs, Unions, Enums

declaring, initializing, typedef, padding, flexible arrays, unions and enums

Structs

core

Declare, initialize, use

#include <stdio.h>

struct point { double x, y; };

typedef struct {
    char name[32];
    int age;
    struct point home;
} Person;

int main(void) {
    Person p = {.name = "Ana", .age = 31, .home = {.x = 1.5, .y = -2.0}};
    Person copy = p;                 // struct assignment copies every member
    copy.age++;
    printf("%s %d %d %.1f\n", p.name, p.age, copy.age, p.home.x);
    Person *pp = &p;
    printf("%s\n", pp->name);        // -> for access through a pointer
}

Compound literals

struct point p = (struct point){.x = 3, .y = 4};
draw_line((struct point){0, 0}, (struct point){10, 10});
int *arr = (int[]){1, 2, 3};

Padding and offsetof

#include <stdio.h>
#include <stddef.h>

struct msg { char tag; double value; short id; };
struct packed_order { double value; short id; char tag; };

int main(void) {
    printf("%zu %zu\n", sizeof(struct msg), sizeof(struct packed_order));
    printf("%zu %zu\n", offsetof(struct msg, value), offsetof(struct msg, id));
}

Flexible array member

struct buffer {
    size_t len;
    unsigned char data[];        // must be last
};
struct buffer *b = malloc(sizeof *b + n);
b->len = n;

Bit-fields

struct flags {
    unsigned int visible : 1;
    unsigned int dirty   : 1;
    unsigned int level   : 4;
};

Unions and Enums

everyday

A tagged union

#include <stdio.h>

enum kind { NUM, TEXT };

struct value {
    enum kind kind;
    union { double num; const char *text; } as;
};

void show(struct value v) {
    switch (v.kind) {
    case NUM:  printf("%g\n", v.as.num); break;
    case TEXT: printf("%s\n", v.as.text); break;
    }
}

int main(void) {
    show((struct value){.kind = NUM, .as.num = 2.5});
    show((struct value){.kind = TEXT, .as.text = "hi"});
}

Enums

enum color { RED, GREEN = 5, BLUE };          // 0, 5, 6
enum status : unsigned char { OK, FAIL };     // C23: fixed underlying type
typedef enum { LOW = 1, HIGH = 2 } Level;

Type punning through a union

#include <stdio.h>
#include <stdint.h>

int main(void) {
    union { float f; uint32_t u; } pun = {.f = 1.0f};
    printf("0x%08x\n", (unsigned)pun.u);
}

Linked Structures

everyday

A singly linked list

#include <stdio.h>
#include <stdlib.h>

struct node { int value; struct node *next; };

static struct node *push(struct node *head, int v) {
    struct node *n = malloc(sizeof *n);
    if (!n) return head;
    n->value = v; n->next = head;
    return n;
}

int main(void) {
    struct node *list = NULL;
    for (int i = 1; i <= 4; i++) list = push(list, i * 10);
    for (struct node *n = list; n; n = n->next) printf("%d -> ", n->value);
    puts("NULL");
    while (list) { struct node *next = list->next; free(list); list = next; }
}
11

Functions

declarations, prototypes, parameters, return values, recursion, inline and variadic functions

Declare and Define

core

Prototype first, definition anywhere

#include <stdio.h>

double average(const int *values, size_t n);   // declaration (prototype)

int main(void) {
    int v[] = {4, 8, 15, 16, 23, 42};
    printf("%.2f\n", average(v, 6));
}

double average(const int *values, size_t n) {  // definition
    if (n == 0) return 0.0;
    long sum = 0;
    for (size_t i = 0; i < n; i++) sum += values[i];
    return (double)sum / n;
}

No parameters: (void), or () in C23

int get_count(void);    // takes no arguments, in every standard
int get_count();        // C23: the same; before C23: unspecified parameters

Arguments are copied

#include <stdio.h>

void try_change(int n, int *p) { n = 99; *p = 99; }

int main(void) {
    int a = 1, b = 1;
    try_change(a, &b);
    printf("%d %d\n", a, b);
}

More Function Features

everyday

Return several values through a struct

typedef struct { int quot, rem; bool ok; } DivResult;

DivResult divide(int a, int b) {
    if (b == 0) return (DivResult){.ok = false};
    return (DivResult){a / b, a % b, true};
}

Recursion

#include <stdio.h>

unsigned long long factorial(unsigned n) { return n <= 1 ? 1 : n * factorial(n - 1); }

int main(void) {
    for (unsigned n = 0; n <= 20; n += 5) printf("%u! = %llu\n", n, factorial(n));
}

static and inline functions

static int helper(int x) { return x * 2; }     // visible only in this file
static inline int square(int x) { return x * x; }   // a hint to inline; put it in headers

Variadic functions

#include <stdarg.h>
#include <stdio.h>

int sum(int count, ...) {
    va_list ap;
    va_start(ap, count);
    int total = 0;
    for (int i = 0; i < count; i++) total += va_arg(ap, int);
    va_end(ap);
    return total;
}

int main(void) { printf("%d\n", sum(4, 1, 2, 3, 4)); }

Functions that never return

[[noreturn]] void die(const char *msg) {
    fprintf(stderr, "fatal: %s\n", msg);
    exit(EXIT_FAILURE);
}

Error Handling Conventions

everyday

Return a status, pass results through pointers

#include <stdio.h>
#include <errno.h>
#include <string.h>

typedef enum { OK = 0, ERR_ARG = -1, ERR_IO = -2 } status;

static status read_count(const char *path, int *out) {
    if (!path || !out) return ERR_ARG;
    FILE *f = fopen(path, "r");
    if (!f) return ERR_IO;
    int ok = fscanf(f, "%d", out) == 1;
    fclose(f);
    return ok ? OK : ERR_IO;
}

int main(void) {
    int n;
    status st = read_count("/nonexistent/count.txt", &n);
    printf("status %d: %s\n", st, st == ERR_IO ? strerror(errno) : "ok");
}
12

Function Pointers

declaring, calling, arrays of handlers, callbacks, qsort and typedefs

Declare and Call

core

A pointer to a function

#include <stdio.h>

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

int main(void) {
    int (*op)(int, int) = add;     // no & needed
    printf("%d\n", op(3, 4));      // no * needed to call
    op = mul;
    printf("%d\n", op(3, 4));
}

A typedef makes it readable

typedef int (*binary_op)(int, int);
typedef void handler_fn(int signal);      // a function type; use handler_fn *

binary_op table[] = {add, mul};
void on_signal(handler_fn *h);

Callbacks

core

qsort with a comparator

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int by_value(const void *a, const void *b) {
    int x = *(const int *)a, y = *(const int *)b;
    return (x > y) - (x < y);              // avoids overflow of x - y
}

static int by_name(const void *a, const void *b) {
    return strcmp(*(const char *const *)a, *(const char *const *)b);
}

int main(void) {
    int v[] = {42, -7, 15, 3};
    qsort(v, 4, sizeof v[0], by_value);
    for (int i = 0; i < 4; i++) printf("%d ", v[i]);
    const char *names[] = {"pear", "apple", "fig"};
    qsort(names, 3, sizeof names[0], by_name);
    printf("\n%s %s %s\n", names[0], names[1], names[2]);
}

A callback with context

typedef void (*visit_fn)(int value, void *ctx);

void for_each(const int *a, size_t n, visit_fn f, void *ctx) {
    for (size_t i = 0; i < n; i++) f(a[i], ctx);
}

static void add_to(int v, void *ctx) { *(long *)ctx += v; }

long total = 0;
for_each(values, n, add_to, &total);

A dispatch table

typedef int (*cmd_fn)(int argc, char **argv);
struct command { const char *name; cmd_fn run; };

static const struct command commands[] = {
    {"list", cmd_list},
    {"add",  cmd_add},
    {"rm",   cmd_remove},
};

for (size_t i = 0; i < sizeof commands / sizeof commands[0]; i++)
    if (strcmp(argv[1], commands[i].name) == 0) return commands[i].run(argc - 1, argv + 1);

More Callback Patterns

advanced

A state machine with a table of handlers

#include <stdio.h>

typedef enum { IDLE, RUNNING, DONE, STATE_COUNT } state;
typedef state (*step_fn)(int input);

static state on_idle(int in) { return in > 0 ? RUNNING : IDLE; }
static state on_running(int in) { return in == 0 ? DONE : RUNNING; }
static state on_done(int in) { (void)in; return DONE; }

static const step_fn steps[STATE_COUNT] = {on_idle, on_running, on_done};
static const char *names[STATE_COUNT] = {"IDLE", "RUNNING", "DONE"};

int main(void) {
    state s = IDLE;
    int inputs[] = {0, 5, 3, 0, 9};
    for (int i = 0; i < 5; i++) { s = steps[s](inputs[i]); printf("%s ", names[s]); }
    putchar('\n');
}
13

Preprocessor

include, define, macros, conditional compilation, stringizing, token pasting and #embed

Includes and Macros

core

Includes

#include <stdio.h>        // search the system directories
#include "config.h"       // search next to this file first, then the system directories
#if __has_include(<threads.h>)
#include <threads.h>
#endif

Object and function-like macros

#define BUFFER_SIZE 4096
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))
#define COUNT(arr) (sizeof (arr) / sizeof (arr)[0])

Multi-statement macros

#define LOG_ERR(msg) do { fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, (msg)); } while (0)

if (failed) LOG_ERR("open failed");   // works with a following else
else proceed();

Conditional Compilation

everyday

#if, #ifdef, #elifdef

#ifdef DEBUG
    fprintf(stderr, "x = %d\n", x);
#endif

#if defined(_WIN32)
#  include <windows.h>
#elif defined(__linux__)
#  include <unistd.h>
#elifdef __APPLE__
#  include <TargetConditionals.h>
#else
#  error "unsupported platform"
#endif

Predefined macros

#include <stdio.h>

int main(void) {
    printf("%s:%d in %s\n", __FILE__, __LINE__, __func__);
    printf("C version %ld\n", __STDC_VERSION__);
#ifdef __GNUC__
    printf("GCC compatible compiler %d.%d\n", __GNUC__, __GNUC_MINOR__);
#endif
}

Advanced Macros

advanced

Stringizing and token pasting

#include <stdio.h>

#define STR(x) #x
#define XSTR(x) STR(x)
#define VERSION 3
#define FIELD(name) int field_##name

struct cfg { FIELD(width); FIELD(height); };

int main(void) {
    printf("%s %s\n", STR(VERSION), XSTR(VERSION));
    struct cfg c = {.field_width = 800};
    printf("%d\n", c.field_width);
}

Variadic macros

#define LOG(fmt, ...) fprintf(stderr, "[%s] " fmt "\n", __func__ __VA_OPT__(,) __VA_ARGS__)
LOG("starting");
LOG("loaded %d items from %s", n, path);

Embed a binary file

static const unsigned char logo_png[] = {
#embed "logo.png"
};
static const size_t logo_png_len = sizeof logo_png;

See what the preprocessor produced

gcc -E main.c | less
gcc -dM -E - < /dev/null | sort     # every predefined macro of the compiler
14

Storage & Linkage

scope, lifetime, static, extern, global variables and thread_local

Scope and Lifetime

core

A static local keeps its value

#include <stdio.h>

int next_id(void) {
    static int id = 0;        // initialized once, lives for the whole program
    return ++id;
}

int main(void) {
    int a = next_id();
    int b = next_id();
    int c = next_id();
    printf("%d %d %d\n", a, b, c);
}

static and extern at file level

// config.c
int verbose = 0;                 // definition, external linkage
static int cache_size = 64;      // internal linkage: private to config.c

// config.h
extern int verbose;              // declaration only: defined in config.c

// main.c
#include "config.h"
verbose = 1;

Storage durations

DeclaredStorageInitialized
inside a blockautomatic, until the block endsnot initialized unless you do it
static inside a blockstatic, whole programonce, to zero if no initializer
at file scopestatic, whole programonce, to zero if no initializer
thread_localone copy per threadwhen the thread starts
mallocallocated, until freenot initialized (calloc zeroes)

thread_local

thread_local int errors_this_thread = 0;   // C23 keyword; _Thread_local in C11

Shadowing and Initialization

everyday

Static and automatic initialization

#include <stdio.h>

int global_count;                 // zero initialized

int main(void) {
    static double table[3];       // zero initialized
    int local = 0;                // must be initialized explicitly
    printf("%d %.1f %d\n", global_count, table[2], local);
    {
        int local = 5;            // shadows the outer local: -Wshadow warns
        printf("%d\n", local);
    }
    printf("%d\n", local);
}

Header only constants and inline functions

// limits.h of your project
static const int MAX_USERS = 100;                 // one private copy per .c file
static inline int clamp(int v, int lo, int hi) { return v < lo ? lo : v > hi ? hi : v; }
extern const char *const APP_NAME;                // one shared definition in a .c file
15

const, volatile, restrict

qualifiers, const correctness, volatile for hardware and signals, restrict and constexpr

const

core

const correctness in function parameters

size_t count_spaces(const char *s);          // promises not to modify *s
void fill(int *out, size_t n, int value);    // writes through out
void copy(char *restrict dst, const char *restrict src, size_t n);

constexpr objects

constexpr int COLUMNS = 80;
constexpr double PI = 3.141592653589793;
char line[COLUMNS];              // a real constant: a normal array, not a VLA

volatile and restrict

everyday

volatile: every access really happens

volatile uint32_t *const status = (uint32_t *)0x40021000;   // a hardware register
while ((*status & READY) == 0) { }                          // re-read on every loop
volatile sig_atomic_t stop = 0;                             // set from a signal handler

restrict: no overlap, faster code

void add_arrays(size_t n, float *restrict out, const float *restrict a, const float *restrict b) {
    for (size_t i = 0; i < n; i++) out[i] = a[i] + b[i];
}

const in Practice

everyday

What const does and does not prevent

#include <stdio.h>

static size_t count_char(const char *s, char c) {
    size_t n = 0;
    for (; *s; s++) if (*s == c) n++;        // moving s is fine; changing *s is not
    return n;
}

int main(void) {
    const int limit = 3;
    int *sneaky = (int *)&limit;              // casting const away compiles...
    printf("%zu\n", count_char("banana", 'a'));
    (void)sneaky;                             // ...but writing through it is undefined behavior
}

volatile is not atomic

volatile int ready = 0;    // wrong for threads: no ordering, no atomicity
atomic_int ready2 = 0;     // right: use <stdatomic.h> between threads
16

printf & scanf

format specifiers, width and precision, reading input safely

printf

core

The common conversions

SpecifierArgumentExample output
%d %iint-42
%uunsigned int42
%ld %lldlong, long long9000000000
%zusize_t16
%tdptrdiff_t-3
%x %X %ounsigned, in hex or octalff FF 377
%bunsigned, in binary (C23)1010
%f %e %gdouble (and float, promoted)3.140000 3.140000e+00 3.14
%cint holding a characterA
%schar * to a stringhello
%pvoid *0x7ffd5b2c
%%none%

Width, precision and flags

#include <stdio.h>

int main(void) {
    printf("[%5d] [%-5d] [%05d]\n", 42, 42, 42);
    printf("[%.2f] [%8.3f] [%+.1e]\n", 3.14159, 3.14159, 31415.9);
    printf("[%x] [%#x] [%08X]\n", 255, 255, 255);
    printf("[%.3s] [%10s] [%-10s]\n", "abcdef", "right", "left");
    printf("[%*d] [%.*f]\n", 6, 7, 2, 1.23456);
}

Fixed width integers

#include <stdio.h>
#include <inttypes.h>

int main(void) {
    int64_t big = -9000000000;
    uint32_t mask = 0xDEADBEEF;
    size_t n = sizeof big;
    printf("%" PRId64 " %" PRIx32 " %zu\n", big, mask, n);
}

stdout, stderr and buffering

fprintf(stderr, "error: %s\n", msg);   // unbuffered: shows up immediately
printf("progress...");                 // line buffered on a terminal, fully buffered in a pipe
fflush(stdout);                        // push it out now
setvbuf(stdout, NULL, _IONBF, 0);      // turn buffering off

Reading Input

core

Read a line safely with fgets

char line[256];
if (fgets(line, sizeof line, stdin)) {
    line[strcspn(line, "\n")] = '\0';     // remove the newline, if any
    printf("you typed: %s\n", line);
}

scanf and its return value

int age;
double weight;
if (scanf("%d %lf", &age, &weight) != 2) {
    fprintf(stderr, "expected a number and a weight\n");
    return 1;
}

Parse a line with sscanf

#include <stdio.h>

int main(void) {
    const char *line = "2026-09-29 temp=21.5 unit=C";
    int y, m, d;
    double t;
    char unit[8];
    int got = sscanf(line, "%d-%d-%d temp=%lf unit=%7s", &y, &m, &d, &t, unit);
    printf("%d fields: %d/%d/%d %.1f %s\n", got, d, m, y, t, unit);
}

Read characters until EOF

int c;                                   // int, not char, so EOF fits
while ((c = getchar()) != EOF) {
    putchar(toupper(c));
}
17

File I/O

fopen modes, reading and writing text and binary files, errors and seeking

Open and Close

core

fopen modes

ModeOpens forIf the file existsIf missing
"r"readingread from the startfails
"w"writingtruncated to zerocreated
"a"appendingwrites go to the endcreated
"r+"reading and writingkeptfails
"w+"reading and writingtruncatedcreated
"wx"writing, exclusive (C11)failscreated
add bbinary, no newline translation on Windows

Always check fopen

#include <stdio.h>
#include <errno.h>
#include <string.h>

int main(void) {
    FILE *f = fopen("missing.txt", "r");
    if (!f) {
        fprintf(stderr, "cannot open missing.txt: %s\n", strerror(errno));
        return 1;
    }
    fclose(f);
}

Text Files

core

Read line by line

FILE *f = fopen("data.txt", "r");
if (!f) { perror("data.txt"); return 1; }
char line[512];
int n = 0;
while (fgets(line, sizeof line, f)) {
    n++;
}
if (ferror(f)) perror("read");
fclose(f);

Write and append

FILE *log = fopen("app.log", "a");
if (log) {
    fprintf(log, "%s started\n", name);
    fclose(log);
}

Lines of any length

char *line = NULL;
size_t cap = 0;
ssize_t len;
while ((len = getline(&line, &cap, f)) != -1) {
    process(line, (size_t)len);
}
free(line);

Binary Files

everyday

fread and fwrite

struct record { int32_t id; double value; };
struct record rs[100];
FILE *f = fopen("records.bin", "wb");
size_t written = fwrite(rs, sizeof rs[0], 100, f);
fclose(f);

f = fopen("records.bin", "rb");
size_t got = fread(rs, sizeof rs[0], 100, f);
fclose(f);

File size and seeking

fseek(f, 0, SEEK_END);
long size = ftell(f);
rewind(f);
fseek(f, 128, SEEK_SET);

Read a whole file into memory

char *slurp(const char *path, size_t *out_len) {
    FILE *f = fopen(path, "rb");
    if (!f) return NULL;
    char *buf = NULL;
    size_t len = 0, cap = 0, n;
    char chunk[8192];
    while ((n = fread(chunk, 1, sizeof chunk, f)) > 0) {
        if (len + n + 1 > cap) {
            size_t ncap = cap ? cap * 2 : 16384;
            while (ncap < len + n + 1) ncap *= 2;
            char *tmp = realloc(buf, ncap);
            if (!tmp) { free(buf); fclose(f); return NULL; }
            buf = tmp; cap = ncap;
        }
        memcpy(buf + len, chunk, n);
        len += n;
    }
    fclose(f);
    if (buf) buf[len] = '\0';
    *out_len = len;
    return buf;
}

Files in Practice

everyday

Write, then read back

#include <stdio.h>

int main(void) {
    FILE *f = tmpfile();                       // a temporary file, removed at close
    if (!f) { perror("tmpfile"); return 1; }
    for (int i = 1; i <= 3; i++) fprintf(f, "line %d\n", i);
    rewind(f);
    char buf[64];
    int n = 0;
    while (fgets(buf, sizeof buf, f)) { n++; fputs(buf, stdout); }
    printf("%d lines, error=%d, eof=%d\n", n, ferror(f) != 0, feof(f) != 0);
    fclose(f);
}
18

stdlib & Conversions

strtol, strtod, errno, random numbers, sorting, searching and environment

String to Number

core

strtol with full error checking

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

int parse_int(const char *s, int *out) {
    char *end;
    errno = 0;
    long v = strtol(s, &end, 10);
    if (end == s || *end != '\0') return -1;          // no digits, or trailing junk
    if (errno == ERANGE || v < INT_MIN || v > INT_MAX) return -2;
    *out = (int)v;
    return 0;
}

int main(void) {
    const char *tests[] = {"42", "12abc", "", "99999999999"};
    for (int i = 0; i < 4; i++) {
        int v = 0;
        int rc = parse_int(tests[i], &v);
        printf("\"%s\" -> rc %d, value %d\n", tests[i], rc, v);
    }
}

Other conversions

double d = strtod("3.5e2", NULL);
unsigned long long big = strtoull("18446744073709551615", NULL, 10);
long hex = strtol("ff", NULL, 16);
long any = strtol("0x1F", NULL, 0);        // base 0: detects 0x and leading 0
char buf[32];
snprintf(buf, sizeof buf, "%d", 42);       // number to string

Random Numbers

everyday

rand, and better options

srand((unsigned)time(NULL));                // seed once
int die = rand() % 6 + 1;                   // 1..6, fine for games
arc4random_uniform(6);                      // BSD, macOS, glibc 2.36+: good quality, no seeding
getrandom(buf, sizeof buf, 0);              // Linux: bytes for keys and tokens

Sorting, Searching, Math Helpers

everyday

bsearch on a sorted array

int key = 23;
int *found = bsearch(&key, sorted, n, sizeof sorted[0], by_value);
if (found) printf("at index %td\n", found - sorted);

Integer helpers

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    div_t r = div(17, 5);
    printf("%d %d %d %ld\n", r.quot, r.rem, abs(-9), labs(-9L));
}

Environment and processes

const char *home = getenv("HOME");        // NULL if not set
if (!home) home = "/tmp";
int status = system("ls -l");             // runs /bin/sh -c; avoid with user input
exit(EXIT_FAILURE);

Environment and Searching

everyday

bsearch on a sorted array

#include <stdio.h>
#include <stdlib.h>

static int cmp(const void *a, const void *b) { int x = *(const int *)a, y = *(const int *)b; return (x > y) - (x < y); }

int main(void) {
    int v[] = {3, 8, 15, 23, 42, 57};
    int key = 23;
    int *hit = bsearch(&key, v, 6, sizeof v[0], cmp);
    printf("%s at %td\n", hit ? "found" : "missing", hit ? hit - v : -1);
}
19

Math & Numbers

math.h functions, rounding, overflow checks, NaN and infinity, complex numbers

math.h

core

The everyday functions

#include <stdio.h>
#include <math.h>

int main(void) {
    printf("%.4f %.1f %.4f %.4f\n", sqrt(2.0), pow(2, 10), exp(1.0), log(100) / log(10));
    printf("%.1f %.1f %.1f %.1f\n", floor(-2.5), ceil(-2.5), round(-2.5), trunc(-2.5));
    printf("%.4f %.1f %.4f\n", atan2(1, 1), fmod(10.5, 3), hypot(3, 4) - 5);
}

NaN and infinity

#include <stdio.h>
#include <math.h>

int main(void) {
    double z = 0.0;
    double inf = 1.0 / z, nan_v = z / z;
    printf("%f %f\n", inf, nan_v);
    printf("%d %d %d\n", isinf(inf), isnan(nan_v), nan_v == nan_v);
}

Overflow Checks

core

Checked arithmetic

#include <stdio.h>
#include <stdckdint.h>
#include <limits.h>

int main(void) {
    int r;
    if (ckd_add(&r, INT_MAX, 1)) puts("add overflowed");
    size_t bytes;
    if (!ckd_mul(&bytes, (size_t)1000, sizeof(double))) printf("%zu bytes\n", bytes);
}

Complex numbers

#include <stdio.h>
#include <complex.h>

int main(void) {
    double complex z = 1.0 + 2.0 * I;
    double complex w = z * conj(z);
    printf("%.1f%+.1fi |z| = %.4f\n", creal(w), cimag(w), cabs(z));
}
20

Time & Dates

time, clock, struct tm, strftime, elapsed time and sleeping

Current Time

core

Format a timestamp

#include <stdio.h>
#include <time.h>

int main(void) {
    time_t now = 1790690712;            // a fixed moment: 2026-09-29 14:05:12 UTC
    struct tm utc;
    gmtime_r(&now, &utc);
    char buf[64];
    strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", &utc);
    puts(buf);
    strftime(buf, sizeof buf, "%A %d %B %Y", &utc);
    puts(buf);
}

struct tm fields

FieldRangeNote
tm_yearyears since 19002026 is 126
tm_mon0 to 11January is 0
tm_mday1 to 31
tm_hour tm_min tm_sec0-23, 0-59, 0-6060 for a leap second
tm_wday0 to 6Sunday is 0
tm_yday0 to 365
tm_isdstpositive, 0, negativenegative lets mktime decide

Measuring Time

everyday

Elapsed wall time with a monotonic clock

struct timespec t0, t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
work();
clock_gettime(CLOCK_MONOTONIC, &t1);
double ms = (t1.tv_sec - t0.tv_sec) * 1e3 + (t1.tv_nsec - t0.tv_nsec) / 1e6;

CPU time with clock()

clock_t start = clock();
work();
double secs = (double)(clock() - start) / CLOCKS_PER_SEC;

Date arithmetic with mktime

struct tm t = {.tm_year = 2026 - 1900, .tm_mon = 0, .tm_mday = 31, .tm_isdst = -1};
t.tm_mday += 30;              // out of range on purpose
time_t when = mktime(&t);     // normalizes to March 2, 2026
double days = difftime(when, start) / 86400.0;

Durations

everyday

Days between two dates

#include <stdio.h>
#include <time.h>

int main(void) {
    struct tm a = {.tm_year = 2026 - 1900, .tm_mon = 0, .tm_mday = 1, .tm_hour = 12, .tm_isdst = -1};
    struct tm b = {.tm_year = 2026 - 1900, .tm_mon = 8, .tm_mday = 29, .tm_hour = 12, .tm_isdst = -1};
    double days = difftime(mktime(&b), mktime(&a)) / 86400.0;
    printf("%.0f days\n", days);
}
21

C23 Features

nullptr, bool, auto, typeof, constexpr, attributes, checked arithmetic and more

The Headline Changes

core

A C23 program

#include <stdio.h>
#include <stdbit.h>

constexpr int LIMIT = 1'000;

[[nodiscard]] static int clamp(int v) { return v > LIMIT ? LIMIT : v; }

int main(void) {
    auto x = clamp(0b1111'1111'1111);     // auto infers int
    typeof(x) y = x / 2;
    bool ok = true;
    int *p = nullptr;
    static_assert(sizeof(int) >= 4);      // the message is optional now
    printf("%d %d %d %d %u\n", x, y, ok, p == nullptr, stdc_count_ones(255u));
}

What C23 added

FeatureExample
nullptr and nullptr_tint *p = nullptr;
bool keywordsbool ok = true;
type inferenceauto n = 42;
typeof, typeof_unqualtypeof(x) copy = x;
constexpr objectsconstexpr int N = 64;
standard attributes[[nodiscard]] [[maybe_unused]] [[deprecated]] [[fallthrough]] [[noreturn]]
binary literals, digit separators0b1010, 1'000'000
empty initializerstruct s x = {};
#embed, #elifdef, __has_include, __VA_OPT__preprocessor
<stdckdint.h>ckd_add, ckd_sub, ckd_mul
<stdbit.h>stdc_count_ones, stdc_bit_width, stdc_leading_zeros
_BitInt(N)exact width integers
enums with a fixed typeenum e : uint8_t { A, B };
unreachable()in <stddef.h>
memset_explicitzeroing that is not optimized away
strdup, strndup, memccpynow in <string.h>
%b in printfbinary output

Attributes

everyday

The standard attributes

[[nodiscard("check the error code")]] int save(const char *path);
[[deprecated("use save() instead")]] int write_file(const char *path);
void handler(int sig, [[maybe_unused]] void *ctx);
switch (x) { case 1: work(); [[fallthrough]]; case 2: more(); }
[[unsequenced]] int square(int x);       // no side effects, result depends only on x

Test for a feature

#if __STDC_VERSION__ >= 202311L
    /* C23 */
#endif
#if __has_c_attribute(nodiscard)
    /* the attribute is known */
#endif

Smaller Additions

advanced

unreachable and memset_explicit

#include <stddef.h>
#include <string.h>

int weekday_len(int d) {
    switch (d) {
    case 0: return 6;
    case 1: return 7;
    default: unreachable();       // tells the optimizer this cannot happen
    }
}

void wipe_key(unsigned char *key, size_t n) {
    memset_explicit(key, 0, n);   // not removed as a dead store
}

stdbit.h

#include <stdio.h>
#include <stdbit.h>

int main(void) {
    unsigned x = 40;
    printf("%u %u %u %u\n", stdc_count_ones(x), stdc_trailing_zeros(x), stdc_bit_width(x), stdc_bit_ceil(x));
    printf("%d\n", stdc_has_single_bit(64u));
}

C2y, the Next Standard

advanced

Already in GCC and Clang with -std=c2y

outer: for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++)
        if (grid[i][j] == 0) break outer;       // named loops (GCC 15)

if (int fd = open(path, O_RDONLY); fd >= 0) { use(fd); }    // if declarations (GCC 15)

switch (c) { case 'a' ... 'z': lower(); break; }            // case ranges, standard in C2y

size_t n = countof(table);                                  // <stdcountof.h>, GCC 16, Clang 21
22

_Generic & Macros

type-generic selection, generic macros, X macros and safe macro patterns

_Generic

everyday

Choose code by type

#include <stdio.h>

#define type_name(x) _Generic((x), \
    int: "int", long: "long", double: "double", \
    char *: "char *", const char *: "const char *", \
    default: "something else")

int main(void) {
    printf("%s %s %s %s\n", type_name(1), type_name(1L), type_name(1.0), type_name("hi"));
}

A type-generic print

#define print(x) _Generic((x), \
    int: print_int, double: print_double, char *: print_str)(x)

void print_int(int v) { printf("%d\n", v); }
void print_double(double v) { printf("%g\n", v); }
void print_str(char *s) { puts(s); }

Macro Patterns

advanced

X macros: one list, several expansions

#include <stdio.h>

#define COLORS(X) X(RED) X(GREEN) X(BLUE)
#define AS_ENUM(name) name,
#define AS_STRING(name) #name,

enum color { COLORS(AS_ENUM) COLOR_COUNT };
static const char *color_names[] = { COLORS(AS_STRING) };

int main(void) {
    for (int c = 0; c < COLOR_COUNT; c++) printf("%d=%s ", c, color_names[c]);
    putchar('\n');
}

container_of

#define container_of(ptr, type, member) \
    ((type *)((char *)(ptr) - offsetof(type, member)))

struct node { int value; struct list_head link; };
struct node *n = container_of(link_ptr, struct node, link);

Swap with typeof

#define SWAP(a, b) do { typeof(a) tmp_ = (a); (a) = (b); (b) = tmp_; } while (0)

Generic Maximum

advanced

A type-safe max with _Generic

#include <stdio.h>

static inline int max_i(int a, int b) { return a > b ? a : b; }
static inline double max_d(double a, double b) { return a > b ? a : b; }
#define max(a, b) _Generic((a) + (b), int: max_i, double: max_d)(a, b)

int main(void) {
    printf("%d %.1f\n", max(3, 9), max(2.5, 1.0));
}
23

Threads & Atomics

C11 threads, mutexes, condition variables, atomics and POSIX threads

C11 Threads

everyday

Start and join threads, with a mutex

#include <stdio.h>
#include <threads.h>

static int counter = 0;
static mtx_t lock;

static int worker(void *arg) {
    int n = *(int *)arg;
    for (int i = 0; i < n; i++) {
        mtx_lock(&lock);
        counter++;
        mtx_unlock(&lock);
    }
    return 0;
}

int main(void) {
    mtx_init(&lock, mtx_plain);
    thrd_t t[4];
    int n = 100000;
    for (int i = 0; i < 4; i++) thrd_create(&t[i], worker, &n);
    for (int i = 0; i < 4; i++) thrd_join(t[i], NULL);
    printf("%d\n", counter);
    mtx_destroy(&lock);
}

Condition variables

mtx_lock(&m);
while (!ready) cnd_wait(&cv, &m);     // always in a loop: wakeups can be spurious
take_item();
mtx_unlock(&m);

mtx_lock(&m);
ready = true;
cnd_signal(&cv);
mtx_unlock(&m);

Atomics

everyday

Lock-free counters

#include <stdio.h>
#include <stdatomic.h>
#include <threads.h>

static atomic_int hits = 0;

static int worker(void *arg) {
    (void)arg;
    for (int i = 0; i < 100000; i++) atomic_fetch_add_explicit(&hits, 1, memory_order_relaxed);
    return 0;
}

int main(void) {
    thrd_t t[4];
    for (int i = 0; i < 4; i++) thrd_create(&t[i], worker, NULL);
    for (int i = 0; i < 4; i++) thrd_join(t[i], NULL);
    printf("%d\n", atomic_load(&hits));
}

Compare and swap

atomic_int state = 0;
int expected = 0;
if (atomic_compare_exchange_strong(&state, &expected, 1)) {
    /* we moved it from 0 to 1 */
} else {
    /* someone else did; expected now holds the current value */
}

POSIX Threads

everyday

pthreads, the most common API

#include <pthread.h>

void *work(void *arg) { return NULL; }

pthread_t th;
pthread_create(&th, NULL, work, NULL);
pthread_join(th, NULL);
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&m);
pthread_mutex_unlock(&m);

call_once

advanced

Run initialization exactly once

#include <stdio.h>
#include <threads.h>

static once_flag flag = ONCE_FLAG_INIT;
static int config = 0;
static void init(void) { config = 42; puts("init ran"); }

static int worker(void *arg) { (void)arg; call_once(&flag, init); return config; }

int main(void) {
    thrd_t t[3];
    for (int i = 0; i < 3; i++) thrd_create(&t[i], worker, NULL);
    int res;
    for (int i = 0; i < 3; i++) thrd_join(t[i], &res);
    printf("config=%d\n", res);
}
24

Bit Manipulation

masks, set, clear, toggle, shifts, byte order and bit counting

Set, Clear, Test

core

The four operations

#include <stdio.h>

int main(void) {
    unsigned flags = 0;
    flags |= 1u << 3;               // set bit 3
    flags |= 1u << 0;               // set bit 0
    flags &= ~(1u << 0);            // clear bit 0
    flags ^= 1u << 5;               // toggle bit 5
    printf("0x%02x %d %d\n", flags, (flags >> 3) & 1u, (flags & (1u << 4)) != 0);
}

Masks and fields

#define RGB_R(c) (((c) >> 16) & 0xFFu)
#define RGB_G(c) (((c) >> 8) & 0xFFu)
#define RGB_B(c) ((c) & 0xFFu)
uint32_t pack = (r << 16) | (g << 8) | b;
uint32_t low4 = x & 0xFu;               // lowest four bits
uint32_t without_low = x & ~0xFu;

Tricks

bool is_pow2 = x && !(x & (x - 1));
unsigned lowest = x & -x;               // lowest set bit (x unsigned)
x &= x - 1;                             // clear the lowest set bit
int bits = __builtin_popcount(x);       // GCC and Clang; C23: stdc_count_ones(x)

Shifts and Byte Order

everyday

Shifting rules

ExpressionResult
x << n with n greater or equal to the widthundefined behavior
1 << 31 on a 32-bit intundefined behavior: use 1u << 31
-8 >> 1implementation defined, -4 on every mainstream compiler
u >> n on unsignedfills with zeros
rotate left by nshift left by n, OR with a right shift by 32 - n, for 0 < n < 32; C2y adds stdc_rotate_left

Byte order

#include <stdio.h>
#include <stdint.h>
#include <string.h>

int main(void) {
    uint32_t v = 0x11223344;
    unsigned char b[4];
    memcpy(b, &v, 4);
    printf("%02x %02x %02x %02x\n", b[0], b[1], b[2], b[3]);
    uint32_t be = ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | b[3];
    printf("0x%08x\n", be);
}

Bit Sets

everyday

A bit set for many flags

#include <stdio.h>
#include <stdint.h>
#include <limits.h>

#define WORD_BITS (sizeof(uint64_t) * CHAR_BIT)
#define SET(bs, i)   ((bs)[(i) / WORD_BITS] |= (uint64_t)1 << ((i) % WORD_BITS))
#define TEST(bs, i)  (((bs)[(i) / WORD_BITS] >> ((i) % WORD_BITS)) & 1u)

int main(void) {
    uint64_t seen[4] = {0};               // 256 flags
    SET(seen, 3); SET(seen, 64); SET(seen, 200);
    printf("%d %d %d %d\n", (int)TEST(seen, 3), (int)TEST(seen, 4), (int)TEST(seen, 64), (int)TEST(seen, 200));
}
25

Build Systems

Make, CMake, pkg-config, warnings in CI, static analysis and formatting

Make

core

A minimal Makefile

CC ?= cc
CFLAGS ?= -std=c17 -Wall -Wextra -O2
app: main.o util.o
	$(CC) $(CFLAGS) $^ -o $@
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@
clean:
	rm -f app *.o

Everyday make commands

make                  # build the first target
make -j$(nproc)       # build in parallel
make clean
make CFLAGS="-O0 -g"  # override a variable
make -n               # print the commands without running them

CMake

core

Configure, build, test

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failure
cmake --install build --prefix /usr/local

Build type flags

CMAKE_BUILD_TYPEFlags with GCC
Debug-g
Release-O3 -DNDEBUG
RelWithDebInfo-O2 -g -DNDEBUG
MinSizeRel-Os -DNDEBUG

Quality Tools

everyday

Format, analyze, lint

clang-format -i src/*.c include/*.h
clang-tidy src/main.c -- -std=c17 -Iinclude
cppcheck --enable=warning,performance,portability src/
gcc -fanalyzer -c main.c                   # GCC's static analyzer
scan-build make                            # Clang's static analyzer

Find library flags with pkg-config

pkg-config --cflags --libs sqlite3 libcurl
gcc main.c $(pkg-config --cflags --libs sqlite3) -o app

Libraries

everyday

Static and shared libraries

gcc -c -O2 util.c -o util.o
ar rcs libutil.a util.o                       # static library
gcc main.c -L. -lutil -o app                  # linked into app

gcc -shared -fPIC -O2 util.c -o libutil.so    # shared library
gcc main.c -L. -lutil -Wl,-rpath,'$ORIGIN' -o app
ldd ./app                                     # which shared libraries it loads
26

Debugging & Sanitizers

gdb, lldb, AddressSanitizer, UBSan, Valgrind, assert and core dumps

Sanitizers First

core

AddressSanitizer and UBSan

gcc -g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer main.c -o app
./app

What each sanitizer finds

FlagFindsCannot combine with
-fsanitize=addressbuffer overflows, use after free, double free, leaksthread, memory
-fsanitize=undefinedsigned overflow, bad shifts, null dereference, misaligned accessnothing: combine with address
-fsanitize=threaddata races between threadsaddress, memory
-fsanitize=memory (Clang)reads of uninitialized memoryaddress, thread
-fsanitize=leakleaks only, very low overheadthread, memory

gdb

core

The commands you need

gdb --args ./app input.txt
(gdb) break parse_line          # stop when parse_line is called
(gdb) run
(gdb) next                      # next line, stepping over calls
(gdb) step                      # into the call
(gdb) print *ptr                # inspect values
(gdb) print arr[0]@5            # five elements of an array
(gdb) bt                        # backtrace: where am I, who called me
(gdb) frame 2                   # look at the caller's variables
(gdb) watch total               # stop when total changes
(gdb) continue

Crash in production: core dumps

ulimit -c unlimited
./app                           # Segmentation fault (core dumped)
gdb ./app core                  # or: coredumpctl gdb on systemd systems
(gdb) bt full

Other Tools

everyday

Valgrind memcheck

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./app

assert and debug prints

#include <assert.h>
assert(n > 0 && "n must be positive");     // aborts with file and line when false
#ifndef NDEBUG
#define DBG(fmt, ...) fprintf(stderr, "%s:%d: " fmt "\n", __FILE__, __LINE__ __VA_OPT__(,) __VA_ARGS__)
#else
#define DBG(...) ((void)0)
#endif
27

Undefined Behavior

the common undefined behaviors, what compilers do with them and how to avoid them

What Undefined Means

core

Three kinds of "it depends"

KindMeaningExample
undefined behaviorthe standard places no requirements; anything can happensigned overflow, out of bounds access
implementation definedeach compiler picks and documents one behaviorright shift of a negative int, sizeof(long)
unspecifiedone of several allowed outcomes, not documentedorder of evaluating function arguments

The optimizer assumes it never happens

int check(int x) {
    if (x + 1 < x) return 1;      // "overflow check" on a signed int
    return 0;
}

The Usual Suspects

core

Undefined behaviors you will actually meet

BehaviorSafe alternative
signed integer overflowcheck first, use unsigned, or ckd_add
reading past the end of an arraypass lengths; test with ASan
using memory after freeset pointers to NULL; single owner
reading an uninitialized localinitialize at declaration
dereferencing a null pointercheck return values of malloc, fopen
shift by the width or more, or into the sign bitunsigned types, check the count
modifying a string literalcopy into an array first
modifying a variable twice without a sequence pointone change per statement
accessing an object through an incompatible pointer typememcpy or a union
returning the address of a localreturn by value, or allocate
a non-void function ending without return, when the caller uses the valuereturn on every path; -Wreturn-type warns

Strict aliasing

float f = 1.0f;
uint32_t bad = *(uint32_t *)&f;            // undefined: reads a float through a uint32_t *
uint32_t good;
memcpy(&good, &f, sizeof good);            // defined, and compiles to one instruction

Catch it at run time

gcc -O2 -g -fsanitize=undefined -fno-sanitize-recover=undefined main.c -o app

Checks That Prevent It

everyday

Safe overflow checks without C23

#include <stdio.h>
#include <limits.h>
#include <stdbool.h>

static bool add_ok(int a, int b) { return !((b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)); }

int main(void) {
    printf("%d %d\n", add_ok(INT_MAX, 1), add_ok(100, 200));
    int r;
    printf("%d\n", __builtin_add_overflow(INT_MAX, 1, &r));
}
28

Secure C

bounds, format strings, integer overflow, input validation, hardening flags and secret handling

Bounds and Formats

core

Dangerous functions and replacements

AvoidWhyUse instead
getsno length limit, removed in C11fgets
strcpy, strcatno destination sizesnprintf, or length checks with memcpy
sprintfno destination sizesnprintf
scanf("%s")no length limit%99s with a width, or fgets
strtokhidden global statestrtok_r
atoi, atofno error reportingstrtol, strtod
printf(user_input)format string attackprintf("%s", user_input)
system with user datashell injectionexecv family with an argument vector

Allocation sizes that cannot overflow

if (count > SIZE_MAX / sizeof *items) return NULL;     // check before multiplying
items = malloc(count * sizeof *items);
size_t total;
if (ckd_mul(&total, count, sizeof *items)) return NULL; // C23

Hardening

everyday

Compiler and linker hardening

gcc -O2 -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fstack-clash-protection \
    -fcf-protection -fPIE -pie -Wl,-z,relro,-z,now main.c -o app
checksec --file=./app

Wipe secrets

unsigned char key[32];
derive_key(key, sizeof key, password);
use_key(key);
memset_explicit(key, 0, sizeof key);      // C23; explicit_bzero on glibc and BSD

The bounds checking interfaces (Annex K)

errno_t e = strcpy_s(dst, sizeof dst, src);

Input Validation

everyday

Validate lengths before copying

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <string.h>

static int set_name(char *dst, size_t cap, const char *src) {
    size_t len = strnlen(src, cap);
    if (len == cap) return -1;          // too long, would not fit with the terminator
    memcpy(dst, src, len + 1);
    return 0;
}

int main(void) {
    char name[8];
    printf("%d %d\n", set_name(name, sizeof name, "Ana"), set_name(name, sizeof name, "Alexandra"));
    printf("%s\n", name);
}
29

Compiler Errors

the error and warning messages you will meet, and what fixes them

Compile Errors

core

The messages and the fixes

MessageCause and fix
implicit declaration of function 'foo'missing #include or prototype; an error since GCC 14
undefined reference to 'foo'declared but not linked: add the .c file, the library (-lm), or fix the name
multiple definition of 'x'a variable defined in a header; define it once in a .c file, declare extern in the header
incompatible pointer typewrong pointer type passed; an error since GCC 14
conflicting types for 'foo'the prototype and the definition differ
expected ';' before '}' tokena missing semicolon on the previous line
control reaches end of non-void functiona path without return
format '%d' expects argument of type 'int'wrong conversion; use %ld, %zu or the PRI macros
'for' loop initial declarations are only allowed in C99 modea very old -std=c89 build
storage size of 'x' isn't knowna struct used before its definition is visible

GCC 14 turned several warnings into errors

int main(void) {
    int *p = 42;             // int-conversion: an error in GCC 14+
    char *s = &p;            // incompatible-pointer-types: an error
    return abs(-1);          // implicit-function-declaration: an error without <stdlib.h>
}

Run Time Failures

everyday

Crashes and what they usually mean

SymptomUsual cause
Segmentation faultnull or dangling pointer, stack overflow, writing to a string literal
Bus errormisaligned access on some CPUs, or a truncated memory mapped file
free(): double free detectedfreeing the same pointer twice
free(): invalid pointerfreeing something not from malloc, or a pointer moved from the start
stack smashing detecteda local array written past its end
Floating point exceptioninteger division by zero, or INT_MIN / -1
the program works in debug and fails with -O2undefined behavior; build with -fsanitize=address,undefined

Warnings Worth Treating as Errors

everyday

What -Wall -Wextra reports, and why it matters

WarningUsually means
-Wformata printf argument of the wrong type
-Wuninitialized, -Wmaybe-uninitializedreading a variable before it has a value
-Wreturn-typea path that falls off the end of a non-void function
-Wsign-comparesigned and unsigned values compared: -1 becomes huge
-Wimplicit-fallthrougha missing break in a switch
-Wshadowan inner variable hides an outer one
-Wconversionan implicit conversion that can change the value
-Wparenthesesan expression whose precedence is easy to misread

A format warning and its fix

size_t n = strlen(s);
printf("%d\n", n);        // warning: format '%d' expects 'int', but argument has type 'size_t'
printf("%zu\n", n);       // correct
30

Standards & Compilers

C89 to C23, C2y, compiler support and C next to C++, Rust and Go

The Standards

core

The C standards and what each brought

StandardYearHighlights
C89 / C901989the first standard: prototypes, void, const
C991999// comments, declarations anywhere, long long, stdint.h, bool, VLAs, designated initializers, inline
C112011_Generic, threads and atomics, static_assert, anonymous structs, aligned memory, removed gets
C172018defect fixes only, no new features
C232024 (ISO/IEC 9899:2024)nullptr, bool keywords, auto, typeof, constexpr, attributes, #embed, checked arithmetic, _BitInt
C2yexpected around 2029_Countof, named loops, if declarations, case ranges

Compiler support in September 2026

CompilerCurrent versionDefault C modeC23 status
GCC16.2gnu23 (since GCC 15)complete for the language
Clang23.1gnu17nearly complete
MSVCVisual Studio 2022 and laterlegacy MSVC C unless /std:c11 or /std:c17partial with /std:clatest
TCC, SDCC, compilers for microcontrollersvariesoften C99 or C11varies

C and Its Neighbors

everyday

When to choose C

QuestionCC++RustGo
memory managementmanualRAII, smart pointersownership checked by the compilergarbage collected
memory safetynone built inpartialsafe by defaultsafe
ABI and FFIthe lingua francacomplexvia C ABIvia cgo
runtime sizetiny, runs on 8-bit microcontrollerssmallsmalla runtime of a few MB
typical usekernels, embedded, libraries, interpretersgames, browsers, large systemsnew systems code, CLI toolsservers, tooling

Checking Your Setup

everyday

Which standard and compiler am I using

#include <stdio.h>

int main(void) {
    printf("__STDC_VERSION__ = %ld\n", __STDC_VERSION__);
#if defined(__clang__)
    printf("clang %d.%d\n", __clang_major__, __clang_minor__);
#elif defined(__GNUC__)
    printf("gcc %d.%d\n", __GNUC__, __GNUC_MINOR__);
#elif defined(_MSC_VER)
    printf("msvc %d\n", _MSC_VER);
#endif
}

What Is a C Cheat Sheet

A C cheat sheet is a one page reference to the syntax, types, operators, standard library functions and tooling of the C programming language, laid out so a programmer finds the right form in seconds instead of searching a book. People also call it a C programming cheat sheet or a C language reference card, and they mean the same mix of syntax, pointers, printf formats and compiler flags.

This C cheat sheet covers 30 sections and 209 snippets for C99, C11, C17 and C23, with a look at C2y. Every snippet that needs a newer standard carries a chip, and the standard picker in the toolbar hides what your compiler mode cannot build. Rows that depend on POSIX, GNU extensions or MSVC carry a chip too, and ISO mode hides them. The printed output of the example programs was produced by compiling and running them with GCC 16.2 on x86-64 Linux.

The toolbox at the top lints C code for the classic bugs, explains any declaration in plain English, checks printf and scanf formats against their arguments, shows how the compiler groups an expression, computes struct layout and padding for four data models, inspects numbers and bit patterns, and turns text into C string literals or byte arrays. The builders write compiler commands with the right warnings, sanitizers and hardening, Makefiles, CMakeLists.txt files, integer type declarations with their printf macros, and debugger sessions. Everything runs in your browser.

How It Differs From a Textbook

A textbook explains C from the ground up; this page shows the shortest correct form of each construct next to the mistake that usually comes with it, such as sizeof on an array parameter, a missing parenthesis in a macro, or signed overflow. It works well next to a linter and the compiler's own warnings. For the language built on top of C, see the C++ cheat sheet; for a memory safe systems language, the Rust cheat sheet.


What This C Cheat Sheet Covers

The 30 sections go from the first compile to shipping safe code, and each link jumps to that section above.


C Syntax Quick Reference

The forms people look up most often, one line each. The sections above show each of them with a full example and the mistakes that come with them.

TaskC
Program entry pointint main(int argc, char *argv[]) { return 0; }
Include a header#include <stdio.h>
Print a lineprintf("%d items\n", n);
Read a linefgets(buf, sizeof buf, stdin);
Declare an arrayint a[10] = {0};
Array lengthsizeof a / sizeof a[0]
Declare a pointerint *p = &x;
Allocate an arrayint *v = malloc(n * sizeof *v);
Free memoryfree(v); v = NULL;
Copy a string safelysnprintf(dst, sizeof dst, "%s", src);
Compare stringsstrcmp(a, b) == 0
String lengthstrlen(s)
Define a struct typetypedef struct { int x, y; } Point;
Initialize a structPoint p = {.x = 1, .y = 2};
Member through a pointerptr->x
Enumerationenum color { RED, GREEN, BLUE };
Function pointerint (*op)(int, int) = add;
Constantconstexpr int N = 64; (C23) or #define N 64
Parse an integerlong v = strtol(s, &end, 10);
Open a fileFILE *f = fopen("data.txt", "r");
Sort an arrayqsort(a, n, sizeof a[0], cmp);
Set a bitflags |= 1u << n;
Test a bit(flags >> n) & 1u
Checked additionif (ckd_add(&r, a, b)) { /* overflow */ }
Compile with warningsgcc -std=c23 -Wall -Wextra -g main.c -o app
Find memory errorsgcc -fsanitize=address,undefined -g main.c

printf Format Specifiers Cheat Sheet

The conversion specifiers most people need, with the argument type each one expects. Passing the wrong type is undefined behavior, which is why GCC and Clang check formats with -Wformat, part of -Wall.

Typeprintfscanf
int%d%d
unsigned int%u, %x%u
long%ld%ld
long long%lld%lld
size_t%zu%zu
int64_t"%" PRId64"%" SCNd64
float%f (promoted to double)%f
double%f, %g, %e%lf
long double%Lf%Lf
char%c" %c"
string%s%99s with a width
pointer%p with (void *)%p

What C Is

C is a general purpose, compiled, statically typed programming language that maps closely to the machine: variables are named memory, pointers are addresses, and a struct is a block of bytes with a known layout. Dennis Ritchie created it at Bell Labs between 1969 and 1973 to rewrite the Unix operating system, and Brian Kernighan and Ritchie's book The C Programming Language (1978) spread it everywhere.

The language is standardized by ISO working group WG14. C23, published as ISO/IEC 9899:2024, is the current standard, and the next revision, called C2y for now, is in progress. GCC 16.2 and Clang 23.1 are the current releases of the two main open source compilers, and GCC compiles C23 by default since version 15.

C runs almost everywhere: operating system kernels such as Linux and Windows NT, embedded firmware on microcontrollers, databases such as SQLite, interpreters for Python and Lua, network servers, and the libraries every other language calls through its foreign function interface.

Where It Fits

Choose C when you need full control of memory and layout, a tiny runtime, maximum portability to unusual hardware, or a stable ABI for a library. For large applications, C++ or Rust usually trade a little of that control for safety and abstraction, and higher level languages such as Go or Python save development time. Much of C's reputation for danger comes from undefined behavior, which the sanitizers and warnings on this page catch early, part of standard software development best practices for any C project.


How a C Program Is Built

Every C source file goes through the same stages on its way to an executable, and knowing them explains most compiler and linker errors.

  1. The preprocessor handles every line starting with #: it pastes in headers, expands macros and drops code inside false #if blocks. gcc -E shows the result.
  2. The compiler parses the preprocessed translation unit, checks types and declarations, reports most errors and warnings, and optimizes the code.
  3. It emits assembly, and the assembler turns that into an object file with machine code and a table of the symbols it defines and needs.
  4. The linker combines the object files and libraries, matches every needed symbol to exactly one definition, and writes the executable. Missing definitions give undefined reference errors; duplicates give multiple definition errors.
  5. At run time, the loader maps the program into memory, resolves shared libraries, sets up the stack and calls main with argc and argv.

When a program fails, place the failure in one of these stages first: a syntax error is step 2, an undefined reference is step 4, and a crash is run time, where the debugging section helps.


C, C++ and Rust Compared

The differences that matter when choosing a systems language.

QuestionCC++Rust
Memory managementmanual malloc and freeRAII, smart pointersownership checked at compile time
Generic codemacros, _Generic, void pointerstemplatesgenerics and traits
Error handlingreturn codes, errnoexceptions or return valuesResult and Option
Standard librarysmalllargelarge, plus crates
Compile timesfastslow for heavy templatesmoderate to slow
Binary interfacestable, the common ABIcompiler specificunstable, uses the C ABI for FFI

Most projects that call C from other languages go through the C ABI, which is why even new libraries often expose a C interface. The Python cheat sheet covers the most common host language for C extensions.


Common C Mistakes

The same handful of bugs shows up in most C code, from homework to production.

In Code

  • Buffer overflows from strcpy, sprintf, gets or scanf("%s") without a width.
  • Off by one errors: forgetting the '\0' of a string, or looping to i <= n.
  • sizeof on an array parameter, which gives the size of a pointer.
  • Using a pointer after free, or freeing it twice.
  • Signed integer overflow, and mixing signed and unsigned values in comparisons.
  • Unparenthesized macros, and macro arguments with side effects evaluated twice.

In Builds and Tooling

  • Compiling without -Wall -Wextra and never reading the warnings.
  • Never running the tests under -fsanitize=address,undefined.
  • Relying on a platform's sizes: long is 8 bytes on Linux and 4 on Windows.
  • Defining variables in headers, which gives multiple definition errors.
  • Ignoring return values of malloc, fopen, fread and scanf.
  • Depending on the evaluation order of function arguments.

How to Learn C Well

Start With Small Programs and Strict Flags

Write small command line programs that read input and print results, and compile every one with -Wall -Wextra -fsanitize=address,undefined from the first day. Learn pointers by drawing memory: every variable a box, every pointer an arrow. Use the Bash cheat sheet for the shell commands around your builds, and keep your exercises in version control with the git cheat sheet.

Then Learn the Machine and the Tools

Read the assembly of small functions on Compiler Explorer, step through programs in gdb, and study undefined behavior until you can spot it in review. An editor with clangd, set up with the VS Code workflow cheat sheet, shows errors as you type. Then read well written C, such as SQLite, Lua or the Linux kernel's lib/ directory.


How This C Cheat Sheet Is Maintained

This page is written and maintained by Bogdan Sandu for TMS Outsource, a software development company. It was checked against the C23 standard and the release notes of GCC 15, GCC 16 and Clang 23 on September 29, 2026, and the output of every example program on the page was produced by compiling and running it with GCC 16.2. If a snippet is wrong or out of date, the date in the byline shows when it was last reviewed.


C Programming Questions People Actually Ask

The questions developers search for most about C and about this cheat sheet, answered in a few sentences each.

Is C still worth learning?

Yes. C runs operating system kernels, embedded devices, databases, language runtimes and the libraries every other language calls, and it is the clearest way to learn how memory, pointers and the machine actually work. That knowledge makes you better at C++, Rust, Go and even Python, where performance problems usually come down to the same memory behavior.

What is the latest version of C?

C23, published as ISO/IEC 9899:2024, is the current standard. It added nullptr, bool as a keyword, auto type inference, typeof, constexpr objects, standard attributes, #embed, checked arithmetic in <stdckdint.h> and _BitInt. The next revision, known as C2y, is in progress, and GCC and Clang already implement parts of it with -std=c2y.

What is the difference between C and C++?

C++ started as C with classes and added templates, exceptions, references, overloading, namespaces and a large standard library. Most C code compiles as C++ with small changes, but the languages have diverged: C has designated initializers everywhere, variably modified types and _Generic, while C++ has RAII and templates. C is smaller and simpler; C++ offers more abstraction at the cost of complexity.

What does a pointer do in C?

A pointer holds the address of another object. With it a function can change the caller's variables, work with arrays and strings, manage memory from malloc, and build linked structures such as lists and trees. The * operator reads or writes the object at the address, and & takes the address of a variable.

How do I find memory leaks and crashes in C?

Compile with -g -fsanitize=address,undefined and run the program: AddressSanitizer reports buffer overflows, use after free, double free and leaks at the exact line. Valgrind finds similar problems without recompiling, and gdb shows where a crash happened with a backtrace. The debugger commands builder on this page writes the sessions for you.

What is undefined behavior in C?

Undefined behavior is any operation for which the standard places no requirements, such as signed integer overflow, reading past an array, dereferencing a null pointer or using freed memory. The program may crash, seem to work, or behave strangely only with optimization on, because compilers assume undefined behavior never happens and optimize on that basis.

Which compiler should I use for C?

GCC and Clang are both excellent, free and available on every major platform; GCC is the default on most Linux distributions and Clang on macOS. On Windows, use Clang or GCC through MSYS2, or MSVC for native Visual Studio projects, keeping in mind that MSVC's C support lags behind. Compile your code with two compilers when you can: each catches different mistakes.

What is the difference between malloc and calloc?

malloc(size) allocates uninitialized memory; calloc(count, size) allocates memory for count elements and sets every byte to zero, and it returns NULL instead of overflowing when count times size is too large. Both must be released with free, and both return NULL when memory runs out.

Is this C cheat sheet up to date?

Yes. It covers C99, C11, C17 and C23 with a look at C2y, and was checked against the C23 standard and the GCC 16.2 and Clang 23.1 releases on September 29, 2026. Every snippet that needs a newer standard carries a chip, the standard picker hides what your compiler mode cannot build, ISO mode hides POSIX, GNU and MSVC only rows, and the example output was produced by a real compiler.

Can I download this C cheat sheet as a PDF?

Yes. The page has a print stylesheet, so your browser's print command with Save as PDF produces a clean copy: the dark theme switches to a print palette, every output and explanation is expanded, and the search box, navigation and footer are left out. The print core button prints only the core snippets for a short reference, and you can collapse sections or run a search first to print only what is still visible.