Compiled and systems languages cheat sheetC logo

C cheat sheet

C syntax on one page, grouped by what you are trying to do: pointers, strings, structs, malloc and free, the preprocessor and gcc flags, checked on C23.

Last updated

C is small, fast and close to the machine: you manage memory yourself, and the compiler trusts you completely. The reference below is grouped by what you are trying to do, and the filter box searches all of it at once. Type malloc and every memory function on the page comes to you, or type C23 to see what the latest standard added.

Every snippet is checked against C23 (ISO/IEC 9899:2024) with GCC 15, which uses C23 by default. Anything that needs a standard newer than C99 says so in the notes column, so you can tell at a glance whether it will build with the compiler you have. Names like buf and count are placeholders for your own. If you do not have GCC installed, the official Docker image is the quickest way to try something: docker run --rm -it -v "$PWD":/src -w /src gcc:15 bash, and the Docker cheat sheet has the rest.

Compiling with gcc

TaskCommandNotes
Check which version you havegcc --version
Compile one file into a programgcc main.c -o mainWithout -o the program is called a.out
Run it./main
Turn on the warnings worth havinggcc -Wall -Wextra -Wpedantic main.c -o main-Wall is not all of them, despite the name
Treat every warning as an errorgcc -Wall -Wextra -Werror main.c -o main
Pick a C standardgcc -std=c23 main.c -o mainOr c17, c11, c99. GCC 15 defaults to gnu23
Build for a debuggergcc -g -O0 main.c -o mainKeeps variable names and line numbers
Build optimisedgcc -O2 main.c -o main-O3 is more aggressive, -Os favours size
Catch memory bugs as they happengcc -g -fsanitize=address,undefined main.c -o mainOut-of-bounds reads, use after free, overflow
Compile several files into one programgcc main.c list.c -o app
Compile only, without linkinggcc -c list.cWrites list.o
Link object filesgcc main.o list.o -o app
Link the maths librarygcc main.c -o main -lmLibraries go after the files that use them
Look for headers in another foldergcc -I include main.c -o main
Link a library from another foldergcc main.c -L lib -lfoo -o mainFinds lib/libfoo.so or lib/libfoo.a
Define a macro from the command linegcc -DDEBUG -DLEVEL=2 main.c -o main
See the code after the preprocessorgcc -E main.c
See the assemblygcc -S -O2 main.cWrites main.s
Bundle objects into a static libraryar rcs libutil.a util.oLink it with -L. -lutil
Build a shared librarygcc -shared -fPIC util.c -o libutil.so
Step through it in a debuggergdb ./mainbreak main, run, next, print x
Find memory leaksvalgrind --leak-check=full ./mainLinux. Build with -g first

On macOS, gcc is Apple's clang under another name unless you install GCC from Homebrew. Every flag on this page works with clang as well.

Types and sizes

TypeExampleSize and notes
Character or tiny integerchar c = 'A';1 byte. Signed or unsigned depends on the platform
Raw byteunsigned char byte = 0xFF;1 byte, 0 to 255
Short integershort s = -300;2 bytes
Integerint n = 42;4 bytes. The standard only promises 16 bits
Long integerlong big = 100000L;8 bytes, but 4 on 64-bit Windows
Long long integerlong long huge = 9000000000LL;8 bytes
Unsigned integerunsigned int u = 42u;Same size, never negative, wraps at the top
Single-precision floatfloat f = 3.14f;4 bytes, about 7 digits. Leave off f and it is a double
Double-precision floatdouble d = 3.14;8 bytes, about 15 digits. The default for decimals
Extended floatlong double ld = 3.14L;8 to 16 bytes depending on the platform
Booleanbool done = false;C23 keyword. Before C23, #include <stdbool.h>
Exact-width integerint32_t id = 7; uint64_t mask = 0;#include <stdint.h>
Size of an object or arraysize_t len = sizeof arr;Unsigned. Print with %zu
Size of a typesizeof(int)Brackets needed for a type, optional for a variable
Pointerint *p = &n;8 bytes on a 64-bit system
Null pointerint *p = nullptr;C23. NULL before that
Read-only variableconst double rate = 0.2;
Compile-time constantconstexpr int MAX_USERS = 100;C23. #define or enum before that
Named integer constantsenum color { RED, GREEN, BLUE };0, 1 and 2 unless you set them
Give a type another nametypedef unsigned long ulong;
Let the compiler infer the typeauto ratio = 1.5;C23. Needs an initialiser
Same type as another variabletypeof(x) y = x;C23
Hex, octal and binary literals0xFF 0755 0b1010Binary is C23. A leading 0 means octal, so 010 is 8
Digit separatorsint million = 1'000'000;C23
Largest and smallest intINT_MAX INT_MIN LLONG_MAX#include <limits.h>
Convert to another type(double)total / countCast one side, or the division is whole-number

The standard fixes minimum sizes, not exact ones. When the width matters, for file formats, network protocols or hashing, use int32_t, uint8_t and friends from stdint.h rather than guessing what int is on the machine it will run on.

printf and scanf formats

PrintCodeNotes
intprintf("%d\n", n);
unsigned intprintf("%u\n", u);
long and long longprintf("%ld %lld\n", l, ll);
size_tprintf("%zu\n", sizeof arr);
int64_t and friendsprintf("%" PRId64 "\n", big);#include <inttypes.h>. PRIu32, PRIx64 too
double or floatprintf("%f\n", d);Six decimal places by default
Two decimal placesprintf("%.2f\n", price);
Scientific or shortest formprintf("%e %g\n", d, d);
A characterprintf("%c\n", c);
A stringprintf("%s\n", name);
First n characters of a stringprintf("%.3s\n", name);
A pointer's addressprintf("%p\n", (void *)p);Cast to void * first
Hex and octalprintf("%x %X %o\n", n, n, n);%#x adds the 0x
Binaryprintf("%b\n", n);C23. glibc 2.35+
A literal percent signprintf("100%%\n");
Pad to a widthprintf("%5d|%-10s|\n", n, name);Minus means left-align
Pad with zerosprintf("%05d\n", 42);Prints 00042
Width from an argumentprintf("%*d\n", width, n);
Print to stderrfprintf(stderr, "error: %s\n", msg);
Read an intif (scanf("%d", &n) != 1) { /* bad input */ }Pass the address, and check the return value
Read a doublescanf("%lf", &d);%lf in scanf, but %f in printf
Read one word, safelyscanf("%63s", word);Width one less than the buffer

A format that does not match its argument is undefined behaviour, not a compile error. -Wall catches most mismatches, which is one more reason to always build with it.

Operators

TaskCodeNotes
Arithmetica + b a - b a * b a / b
Whole-number division7 / 23. Both sides are ints, so it truncates toward zero
Remainder7 % 31. -7 % 3 is -1: the sign follows the left side
Add one before or after reading++i i++Only differs inside a bigger expression
Update in placex += 5; x *= 2; x %= 3;
Comparea == b a != b a < b a >= bGives int 1 or 0
And, or, nota && b a || b !aStops as soon as the answer is known
Pick one of two valuesint max = a > b ? a : b;
Bitwise and, or, xor, nota & b a | b a ^ b ~a
Shift bits1u << 4 n >> 1Shift unsigned values. Shifting by the type's width or more is undefined
Set a bitflags |= 1u << 3;
Clear a bitflags &= ~(1u << 3);
Flip a bitflags ^= 1u << 3;
Test a bitif (flags & (1u << 3)) { }
Address of a variable&x
Value a pointer points to*p
Struct memberpoint.x
Struct member through a pointerptr->xSame as (*ptr).x

The bitwise operators bind more loosely than comparison, so flags & MASK == 0 means flags & (MASK == 0). Bracket every bitwise test: (flags & MASK) == 0.

Control flow

TaskCodeNotes
If, else if, elseif (a) { } else if (b) { } else { }Any non-zero value counts as true
Switchswitch (c) { case 'y': yes(); break; default: no(); }Integers and chars only, not strings
Several cases, one branchcase 'a': case 'e': case 'i': vowels++; break;
Fall through on purpose[[fallthrough]];C23. Silences the warning
Counting loopfor (int i = 0; i < n; i++) { }Declaring i in the loop is C99+
Loop over an arrayfor (size_t i = 0; i < sizeof arr / sizeof arr[0]; i++) { }
While loopwhile (n > 0) { n /= 10; }
Run at least oncedo { } while (retry);
Loop foreverfor (;;) { }Or while (true)
Skip to the next iterationcontinue;
Leave the loopbreak;Only the innermost loop or switch
Jump to cleanup codegoto cleanup; cleanup: free(buf);The accepted use of goto in C
Mark code that cannot be reachedunreachable();C23. #include <stddef.h>. Undefined if it is reached

C has no exceptions. Functions report failure through their return value, often -1 or NULL, and set errno for the detail. Check every return value that can fail, and use goto cleanup to free what you allocated on the way out.

Functions

TaskCodeNotes
Declare a function (prototype)int add(int a, int b);Usually in a header, before any call
Define itint add(int a, int b) { return a + b; }
Take no argumentsint roll(void);In C23 () means the same. Before that it meant any arguments
Return nothingvoid log_line(const char *msg);
Let the function change a variablevoid inc(int *n) { (*n)++; } inc(&count);C passes everything by value, so pass a pointer
Take an arrayint sum(const int *nums, size_t count);The array arrives as a pointer, so pass its length too
Return several valuesbool divide(int a, int b, int *quotient, int *remainder);Out parameters, plus a success flag
Visible in this file onlystatic int helper(void) { }
Keep a value between callsstatic int calls = 0;Inside the function. Starts at zero once
Program entry pointint main(void)Or int main(int argc, char *argv[])
Command-line argumentsargv[0] argv[1] argcargv[0] is the program name
End the program with a statusreturn EXIT_SUCCESS; exit(EXIT_FAILURE);#include <stdlib.h>
Pointer to a functionint (*op)(int, int) = add; op(2, 3);
Name a function pointer typetypedef int (*binop)(int, int);
Any number of argumentsint sum_all(int count, ...);Read them with va_list from <stdarg.h>
Warn if the result is ignored[[nodiscard]] int parse(const char *s);C23
Never returns[[noreturn]] void die(const char *msg);C23. _Noreturn in C11
Suggest inliningstatic inline int square(int x) { return x * x; }C99+. Goes in a header

Pointers and arrays

A pointer holds the address of something else. An array is a fixed block of values side by side, and in most expressions its name turns into a pointer to its first element.

TaskCodeNotes
Declare an arrayint nums[5] = {1, 2, 3, 4, 5};
Let the initialiser set the lengthint nums[] = {1, 2, 3};
Zero every elementint counts[100] = {};C23. {0} works in every version
Set chosen elementsint a[10] = {[2] = 5, [9] = 1};The rest are zero
Count the elementssizeof nums / sizeof nums[0]Only on the array itself, not on a pointer to it
Two-dimensional arrayint grid[3][4]; grid[row][col] = 1;
Take an addressint *p = &x;
Read or write through a pointer*p = 10;
Point at an array's first elementint *p = nums;Same as &nums[0]
Index through a pointerp[2]Same as *(p + 2)
Move to the next elementp++;Moves by sizeof(int) bytes, not 1
Distance between two pointersptrdiff_t gap = end - start;In elements. Same array only
Pointer to a pointerint **pp = &p;
Cannot change the value pointed toconst int *p = &x;
Cannot point anywhere elseint *const p = &x;
Pointer to anythingvoid *any = &x;Cast it back before you dereference it
Check for nullif (p != nullptr) { }Or if (p)
Array whose length is known at run timeint buf[n];Optional since C11. Avoid for a large or untrusted n
Unnamed array in an expressionsum((int[]){1, 2, 3}, 3)Compound literal, C99+

Reading past the end of an array, through a null pointer or after free is undefined behaviour. Nothing checks it for you. The program might crash, carry on with garbage, or work until the day it ships. Build with -fsanitize=address while developing.

Strings

A C string is an array of char ending in a zero byte, written '\0'. Every function here finds the end by looking for that byte, so a string without one is a bug waiting to read the rest of memory.

TaskCodeNotes
A string you can changechar name[] = "Ada";4 bytes: A, d, a and the '\0'
A string you only readconst char *msg = "hello";String literals are read-only
A fixed-size bufferchar buf[64];
Lengthstrlen(s)#include <string.h>. Does not count the '\0'
Are they equalstrcmp(a, b) == 0a == b compares addresses, not text
Compare the first n charactersstrncmp(line, "GET ", 4) == 0
Copy into a buffer, safelysnprintf(dst, sizeof dst, "%s", src);Always ends with '\0', cuts off what does not fit
Copy when you know it fitsstrcpy(dst, src);No bounds check at all
Appendstrcat(dst, src);dst needs room for both plus '\0'
Build a string from partssnprintf(buf, sizeof buf, "%s-%d", name, id);Returns the length it wanted, so you can spot truncation
Find a characterstrchr(email, '@')Pointer to it, or NULL
Find the last occurrencestrrchr(path, '/')
Find a substringstrstr(s, "cat")Pointer to the match, or NULL
Length of the part before any of thesestrcspn(s, " \t")
Remove the newline fgets keptline[strcspn(line, "\n")] = '\0';
Text to a number, with error checkslong n = strtol(s, &end, 10);end points past the digits. strtod for doubles
Text to an int, quick and trustingatoi(s)Returns 0 for bad input, with no way to tell
Copy into new memorychar *copy = strdup(s);C23 in the standard, POSIX before. free it
Split on a separatorfor (char *t = strtok(line, ","); t; t = strtok(nullptr, ",")) { }Writes into line, and is not thread-safe
Test a characterisdigit(c) isalpha(c) isspace(c)#include <ctype.h>. Pass an unsigned char value
Change casetoupper(c) tolower(c)One character at a time

Structs and unions

TaskCodeNotes
Define a structstruct point { int x; int y; };
Create onestruct point p = {1, 2};
Create one by field namestruct point p = {.x = 1, .y = 2};C99+. Fields you leave out are zero
Skip the struct keywordtypedef struct { int x; int y; } Point; Point p;
Read a fieldp.x
Read a field through a pointerptr->xSame as (*ptr).x
Copy onestruct point q = p;Copies every field. Pointers inside are shared
Assign a whole struct at oncep = (struct point){.x = 5, .y = 6};Compound literal, C99+
Compare twoa.x == b.x && a.y == b.y== does not work on structs
Pass without copyingvoid move(struct point *p, int dx);
Pass read-only without copyingdouble length(const struct point *p);
Array of structsstruct point path[10]; path[0].x = 3;
Struct that points to its own typestruct node { int value; struct node *next; };The building block of linked lists and trees
Variable-length data on the endstruct message { size_t len; char data[]; };Flexible array member. Allocate sizeof + len
Fields a few bits widestruct flags { unsigned ready : 1; unsigned mode : 3; };Bit fields. Layout is up to the compiler
Where a field sitsoffsetof(struct point, y)#include <stddef.h>
Define a unionunion number { int i; float f; };All fields share one piece of memory
Union that knows what it holdsstruct value { enum { INT, FLOAT } kind; union { int i; float f; }; };Tagged union. Anonymous union is C11+

sizeof a struct can be bigger than its fields added up, because the compiler pads fields to their alignment. Ordering fields from largest to smallest usually shrinks it.

Dynamic memory (malloc and free)

Memory from malloc lives until you free it, not until the function returns. Everything here needs #include <stdlib.h>, apart from the mem functions, which are in string.h.

TaskCodeNotes
Allocate an arrayint *nums = malloc(count * sizeof *nums);Contents are garbage until you set them
Check it workedif (nums == nullptr) { /* out of memory */ }malloc returns NULL on failure
Allocate and zeroint *nums = calloc(count, sizeof *nums);Also checks count * size for overflow
Allocate one structstruct node *n = malloc(sizeof *n);
Room for a copy of a stringchar *copy = malloc(strlen(s) + 1);+ 1 for the '\0'
Grow or shrinkint *tmp = realloc(nums, new_count * sizeof *nums);May move the block. Old pointer is invalid if it did
Keep the old block if growing failsif (tmp) { nums = tmp; }Never nums = realloc(nums, ...)
Give it backfree(nums); nums = nullptr;Nulling it stops a second free doing damage
Free nothingfree(nullptr);Allowed, and does nothing
Copy bytesmemcpy(dst, src, count * sizeof *src);The two must not overlap
Copy bytes that might overlapmemmove(dst, src, count * sizeof *src);
Fill with a byte valuememset(buf, 0, sizeof buf);Byte by byte, so only 0 or -1 make sense for ints
Compare bytesmemcmp(a, b, size) == 0
Allocate aligned memoryaligned_alloc(64, size)C11. Release it with free

Every malloc, calloc, realloc and strdup needs exactly one free. Two frees of the same pointer, or using it after the free, is undefined behaviour. Writing sizeof *nums rather than sizeof(int) keeps the size right if the type of nums ever changes.

Preprocessor

The preprocessor runs before the compiler and works on text. It knows nothing about types or scope, which is where most macro surprises come from.

TaskCodeNotes
Include a standard header#include <stdio.h>
Include your own header#include "list.h"Searched for next to the current file first
Define a constant#define MAX_LINE 256No semicolon and no =
Function-like macro#define SQUARE(x) ((x) * (x))Bracket every use of x and the whole body
Macro with several statements#define SWAP(a, b) do { int t = (a); (a) = (b); (b) = t; } while (0)do-while(0) makes it safe after an if
Remove a definition#undef MAX_LINE
Compile only when defined#ifdef DEBUG #endifPair with gcc -DDEBUG
Compile only when not defined#ifndef NDEBUG #endif
Else if defined#elifdef _WIN32C23. #elif defined(_WIN32) before that
Test a value#if LEVEL > 1 #elif LEVEL == 1 #else #endif
Header guard#ifndef LIST_H #define LIST_H ... #endifStops a header being included twice
Header guard, short form#pragma onceNot in the standard, but GCC, Clang and MSVC all support it
Turn an argument into a string#define STR(x) #xSTR(hello) becomes "hello"
Glue two tokens together#define CAT(a, b) a##b
Current file, line and function__FILE__ __LINE__ __func____func__ is a variable, not a macro
Which C standard is this__STDC_VERSION__202311L for C23, 201710L for C17
Stop the build with a message#error "Needs a 64-bit platform"
Warn during the build#warning "Deprecated header"C23
Check something at compile timestatic_assert(sizeof(int) == 4);C23 keyword. _Static_assert with a message in C11
Embed a file's bytes in an array#embed "icon.png"C23, GCC 15+. On its own line inside the array's braces

SQUARE(i++) expands to ((i++) * (i++)), which changes i twice and is undefined. A macro argument can be evaluated more than once, so never pass one anything with a side effect. A static inline function avoids the problem entirely.

Common standard library functions

TaskCodeNotes
Print a lineputs("done");stdio.h. Adds the newline for you
Read a line of inputfgets(buf, sizeof buf, stdin)stdio.h. NULL at end of input
Open a fileFILE *f = fopen("data.txt", "r");stdio.h. NULL on failure
File modes"r" "w" "a" "r+" "rb" "wb"w empties the file first. b for binary
Write to a filefprintf(f, "%d\n", n);
Read binary datasize_t got = fread(buf, 1, sizeof buf, f);Returns how many items it read
Write binary datafwrite(buf, 1, len, f);
Close a filefclose(f);Flushes anything still buffered
Print why the last call failedperror("fopen");stdio.h. Uses errno
Error message for an errno valuestrerror(errno)string.h and errno.h
Sort an arrayqsort(arr, count, sizeof arr[0], compare);stdlib.h. Example below
Search a sorted arraybsearch(&key, arr, count, sizeof arr[0], compare)stdlib.h. Pointer to the match, or NULL
Absolute valueabs(n) labs(l) fabs(d)stdlib.h for ints, math.h for fabs
Square root and powersqrt(x) pow(x, 2)math.h. Link with -lm
Roundfloor(x) ceil(x) round(x) trunc(x)math.h. Return doubles
Random number from 1 to 6rand() % 6 + 1stdlib.h. Seed once with srand. Not for security
Seed the random numberssrand((unsigned)time(nullptr));time.h
Current time in secondstime_t now = time(nullptr);time.h
Time a piece of codeclock_t start = clock(); (double)(clock() - start) / CLOCKS_PER_SECtime.h. CPU time, not wall-clock time
Read an environment variableconst char *home = getenv("HOME");stdlib.h. NULL if it is not set
Check an assumption while developingassert(count > 0);assert.h. Removed when built with -DNDEBUG
Exit from anywhereexit(EXIT_FAILURE);stdlib.h. Flushes and closes open files

A growable array, start to finish

Structs, malloc through realloc, error checks and qsort in one program. It grows the buffer by doubling, which is how most dynamic arrays work under the hood.

#include <stdio.h>
#include <stdlib.h>
 
typedef struct {
    int *items;
    size_t len;
    size_t cap;
} IntList;
 
// Returns false when memory runs out, and leaves the list as it was.
bool list_push(IntList *list, int value)
{
    if (list->len == list->cap) {
        size_t new_cap = list->cap ? list->cap * 2 : 8;
        int *grown = realloc(list->items, new_cap * sizeof *grown);
        if (grown == nullptr) {
            return false;
        }
        list->items = grown;
        list->cap = new_cap;
    }
    list->items[list->len++] = value;
    return true;
}
 
void list_free(IntList *list)
{
    free(list->items);
    *list = (IntList){};
}
 
int compare_ints(const void *a, const void *b)
{
    int x = *(const int *)a;
    int y = *(const int *)b;
    return (x > y) - (x < y);  // not x - y, which can overflow
}
 
int main(void)
{
    IntList scores = {};  // C23: empty braces set every member to zero
 
    int input[] = {42, 7, 19, 88, 3};
    for (size_t i = 0; i < sizeof input / sizeof input[0]; i++) {
        if (!list_push(&scores, input[i])) {
            fprintf(stderr, "out of memory\n");
            list_free(&scores);
            return EXIT_FAILURE;
        }
    }
 
    qsort(scores.items, scores.len, sizeof scores.items[0], compare_ints);
 
    for (size_t i = 0; i < scores.len; i++) {
        printf("%d ", scores.items[i]);
    }
    printf("\n");  // 3 7 19 42 88
 
    list_free(&scores);
    return EXIT_SUCCESS;
}

Build it with the sanitisers on while you are still changing it:

gcc -std=c23 -Wall -Wextra -g -fsanitize=address,undefined scores.c -o scores
./scores

realloc(nullptr, size) behaves like malloc, which is why the first push needs no special case. The standard does not say which algorithm qsort uses, or promise that it is stable. C libraries have shipped variants of quick sort and merge sort, and both are on the site as step-through visualisations.

Pointers, start to finish

A function can only change a caller's variable if it is given the variable's address. An array passed to a function arrives as a pointer to its first element, so the length has to travel with it.

#include <stdio.h>
 
void swap(int *a, int *b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
 
int sum(const int *nums, size_t count)
{
    int total = 0;
    for (size_t i = 0; i < count; i++) {
        total += nums[i];  // same as *(nums + i)
    }
    return total;
}
 
int main(void)
{
    int x = 1;
    int y = 2;
    swap(&x, &y);
    printf("%d %d\n", x, y);  // 2 1
 
    int nums[] = {10, 20, 30};
    size_t count = sizeof nums / sizeof nums[0];  // 3, only works on the array itself
    printf("%d\n", sum(nums, count));  // 60
    return 0;
}

Inside sum, sizeof nums would give 8, the size of a pointer, not 12. That is the most common way the element-count trick goes wrong.

Splitting a program across files

Declarations go in a header, definitions in a .c file, and every file that uses them includes the header. The header guard stops it being pasted in twice.

// point.h
#ifndef POINT_H
#define POINT_H
 
typedef struct {
    double x;
    double y;
} Point;
 
double point_distance(Point a, Point b);
 
#endif
// point.c
#include <math.h>
#include "point.h"
 
double point_distance(Point a, Point b)
{
    return hypot(b.x - a.x, b.y - a.y);
}
// main.c
#include <stdio.h>
#include "point.h"
 
int main(void)
{
    Point a = {.x = 0, .y = 0};
    Point b = {.x = 3, .y = 4};
 
    printf("%.1f\n", point_distance(a, b));  // 5.0
    return 0;
}

Compile each .c file to an object file, then link them. Only files that changed need compiling again, which is what make automates.

gcc -Wall -Wextra -c point.c
gcc -Wall -Wextra -c main.c
gcc main.o point.o -o app -lm
./app

Commit the .c and .h files and add *.o and the program to .gitignore. The Git cheat sheet covers the rest of that workflow.

Reading a file line by line

Open, check, loop with fgets, close. strerror(errno) turns the failure into a message like "No such file or directory".

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "usage: %s FILE\n", argv[0]);
        return EXIT_FAILURE;
    }
 
    FILE *f = fopen(argv[1], "r");
    if (f == nullptr) {
        fprintf(stderr, "%s: %s\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    }
 
    char line[256];
    int number = 0;
    while (fgets(line, sizeof line, f) != nullptr) {
        line[strcspn(line, "\n")] = '\0';  // fgets keeps the newline
        printf("%4d  %s\n", ++number, line);
    }
 
    if (ferror(f)) {
        perror(argv[1]);
    }
    fclose(f);
    return EXIT_SUCCESS;
}

A line longer than 255 characters arrives in pieces, one per fgets call. On Linux and macOS, POSIX getline allocates a buffer as big as each line needs. The Linux commands cheat sheet and the Bash cheat sheet cover running programs like this one from the terminal.

Gotchas

The mistakes that turn up in almost every C codebase at some point.

Looks rightWhat actually happensDo this instead
if (x = 5)Assigns 5 to x, and the condition is always trueif (x == 5). -Wall warns about it
if (a == b) on two stringsCompares addresses, so it is false for equal textstrcmp(a, b) == 0
sizeof arr inside a functionThe size of a pointer, because the array decayedPass the length in as a separate argument
char buf[5]; strcpy(buf, "hello");Writes six bytes, one past the endLeave room for the '\0', or use snprintf
double avg = sum / count; with two intsDivides as whole numbers first, so 7 / 2 gives 3.0(double)sum / count
scanf("%d", n)Writes to whatever address n happens to holdscanf("%d", &n)
p = realloc(p, size)Leaks the old block when realloc failsAssign to a temporary, check it, then swap
Using a pointer after free(p)Undefined, and often works until it doesn'tSet p = nullptr straight after free
int x; printf("%d", x);Prints whatever was in that memoryInitialise every local variable
for (unsigned i = n - 1; i >= 0; i--)Never ends, because an unsigned value is always 0 or morefor (size_t i = n; i-- > 0;)
int big = INT_MAX + 1;Signed overflow is undefined, not a wrap to negativeCheck before the add, or use a wider or unsigned type
#define DOUBLE(x) x * 2 then DOUBLE(1 + 2)Expands to 1 + 2 * 2, which is 5#define DOUBLE(x) ((x) * 2)

Common questions

Which version of C does this cheat sheet cover?

C23, the current ISO standard (ISO/IEC 9899:2024), checked with GCC 15, which uses it by default. Most of the page works in any C from C99 onward, and anything that needs a newer standard says so in the notes column: nullptr, bool as a keyword, constexpr and binary literals need C23, and anonymous unions and aligned_alloc need C11. Pass -std=c99, -std=c11 or -std=c17 to gcc to build against an older standard.

What is the difference between char *s and char s[] in C?

char s[] = "hi" creates an array of three bytes that you own and can change. char *s = "hi" creates a pointer to a string literal, which lives in read-only memory, so writing to s[0] is undefined behaviour and usually crashes. sizeof also differs: on the array it gives the array's size, on the pointer it gives the pointer's size, 8 bytes on a 64-bit system. Write const char *s for literals so the compiler stops you writing to them.

Why does my C program crash with a segmentation fault?

A segmentation fault means the program touched memory it is not allowed to. The usual causes are dereferencing a NULL or uninitialised pointer, reading or writing past the end of an array, using memory after free, and a string with no terminating zero byte. Build with gcc -g -fsanitize=address and run it again: the sanitiser prints the exact line and what went wrong, which is far quicker than guessing.

Should I cast the result of malloc in C?

No. malloc returns void *, and C converts void * to any other object pointer type automatically, so the cast adds nothing. In very old C it could also hide a missing #include <stdlib.h>. The cast is required in C++, which is why you see it in code written to compile as both. Write int *p = malloc(n * sizeof *p) instead.

What is undefined behaviour in C?

Code whose result the C standard does not define at all, such as signed integer overflow, reading past the end of an array, using a pointer after free, or changing a variable twice in one expression like i = i++. The compiler is allowed to assume it never happens and optimise on that basis, so the program can crash, print garbage, or appear to work until a different compiler flag changes it. -fsanitize=undefined catches many cases at run time.

What is the difference between gcc and clang?

Both are free, standards-following C compilers that accept almost the same command-line flags, so everything on this page works with either. GCC is the default on most Linux distributions. Clang is the default on macOS, where typing gcc actually runs Apple's clang, and it is known for clear error messages. For learning, pick whichever is already installed.

What is the difference between C and C++?

C++ started as C with classes and has grown into a much larger language, with templates, references, exceptions, RAII and a big standard library of containers. C stays small: structs and functions, manual memory with malloc and free, and no function overloading. Most C code is close to valid C++, but not all of it, since C++ requires the malloc cast, and C has features like designated array initialisers that C++ does not.

Does main need to return 0?

main returns the program's exit status, 0 or EXIT_SUCCESS for success and anything else, usually EXIT_FAILURE, for an error. Since C99, reaching the closing brace of main without a return statement returns 0 automatically, so the return is optional there. It is not optional in any other function that declares a return type.

See all cheat sheets

Want this explained by a cat?

The videos cover the same ground in sixty seconds. If there is a tool you want a cheat sheet for next, ask.