Cheat Sheet Library

C Cheat Sheet

Quick reference for C: compiling with gcc/clang, types, printf formats, pointers, strings, memory and the preprocessor. Targets C17, with C23 additions marked "(C23)".

  • 130 entries
  • 14 sections
  • Updated

130 entries

On this page

Compile & Run #

CommandWhat it does
gcc main.c -o app && ./appCompile main.c into the program app and run it (clang takes the same flags)
gcc -Wall -Wextra -std=c17 main.c -o appTurn on the useful warnings and pick the C standard
-std=c23Use C23 (GCC 14+, Clang 18+; GCC 13: -std=c2x)
-gAdd debug info for gdb / lldb
-O2Optimize for speed (use -O0 -g while debugging)
-lmLink the math library (sqrt, pow on Linux); put it after the source files
gcc -c util.cCompile only, produces util.o
gcc main.o util.o -o appLink object files into one program
-fsanitize=address,undefinedCatch out-of-bounds, use-after-free and UB at run time

Program Structure & Functions #

#include <stdio.h>

int add(int a, int b);            // prototype (declaration)

int main(void) {
    printf("%d\n", add(2, 3));
    return 0;                     // 0 = success
}
int add(int a, int b) { return a + b; }   // definition
CodeWhat it does
int main(int argc, char *argv[])Entry point with command-line arguments (argv[0] = program name)
return EXIT_SUCCESS;Exit code from <stdlib.h> (EXIT_FAILURE for errors)
int f(void);Takes no arguments (before C23, int f(); meant "unspecified arguments")
static int helper(int x)Function visible only inside this .c file
void swap(int *a, int *b)Arguments are copies; pass a pointer to change the caller's variable
static int count = 0; (in a function)Keeps its value between calls
extern int g_total;Declares a global that is defined in another file

Types & Sizes #

TypeWhat it is
char1 byte; a character or small integer ('A')
short / intUsually 2 / 4 bytes
long8 bytes on 64-bit Linux/macOS, 4 bytes on Windows
long longAt least 8 bytes
float / double4 / 8 byte floating point (use double by default)
unsigned intOnly non-negative values (42u)
int8_t int16_t int32_t int64_tExact-width integers from <stdint.h>
uint8_t uint32_t uint64_tExact-width unsigned integers (bytes, registers, bit flags)
size_tUnsigned type for sizes and indexes (sizeof, strlen return it)
booltrue / false (C17: #include <stdbool.h>; built-in keywords in C23)
sizeof(int) / sizeof xSize in bytes of a type / variable
(double)a / bCast: convert before dividing
constexpr int SIZE = 10;True compile-time constant (C23; GCC 13+, Clang 19+)
auto n = 5; / typeof(x) y;Type inference / copy a type (C23)
0b1010 / 1'000'000Binary literal / digit separators (C23)

printf & scanf Format Specifiers #

SpecifierType
%d / %iint
%uunsigned int
%ld / %lldlong / long long (%lu, %llu for unsigned)
%zusize_t (result of sizeof, strlen)
%fdouble in printf (float is promoted to it); float in scanf
%lfdouble in scanf (required); same as %f in printf
%e / %gScientific notation / shortest of %f and %e
%cchar
%sString (char *)
%pPointer address; cast to (void *)
%x / %X / %oHexadecimal (lower / upper case) / octal
%%A literal % sign
%5d / %-5d / %05dWidth 5: right-aligned / left-aligned / zero-padded
%.2f2 digits after the decimal point
"%" PRId64Print int64_t portably (<inttypes.h>; also PRIu32 etc.)
scanf("%d", &n) == 1Read an int; pass the address and check how many items were read
scanf("%19s", name)Read a word of at most 19 chars into char name[20] (no & for arrays)

Operators #

OperatorWhat it does
+ - * / %Arithmetic; 7 / 2 is 3 (integer division), 7 % 2 is 1
7 / 2.03.5: one floating operand gives floating division
++i / i++Increment, returns the new / old value
== != < > <= >=Comparison, gives 1 or 0
&& || !Logical and / or / not (short-circuit)
a & b / a | b / a ^ b / ~aBitwise and / or / xor / not
a << n / a >> nShift left / right by n bits
flags |= 1u << 3Set bit 3
flags &= ~(1u << 3)Clear bit 3
flags ^= 1u << 3Toggle bit 3
(flags >> 3) & 1uRead bit 3
cond ? a : ba if cond is true, otherwise b

Control Flow #

if (x > 0) { ... } else if (x == 0) { ... } else { ... }
for (int i = 0; i < n; i++) { if (i == 2) continue; if (i == 8) break; }
while (running) { ... }
do { ... } while (again);      // runs at least once
switch (cmd) {
    case 'q': quit(); break;   // without break it falls through
    case 'h': case '?': help(); break;
    default: puts("unknown");
}

Arrays & Strings #

CodeWhat it does
int a[5] = {1, 2, 3};Array of 5; the rest is zero-filled
sizeof a / sizeof a[0]Number of elements (only where a is a real array, not a pointer)
int grid[3][4];2D array: 3 rows of 4
char s[] = "hi";Modifiable string: {'h', 'i', '\0'}
const char *s = "hi";Pointer to a read-only string literal
strlen(s)Length without the '\0' (needs <string.h>)
strcmp(a, b) == 0Strings are equal (a == b only compares addresses)
snprintf(buf, sizeof buf, "%s-%d", name, id)Safe formatted write; always '\0'-terminates and truncates
strcpy(dst, src)Copy; overflows if dst is too small
strncpy(dst, src, n)Adds no '\0' when src is too long: set dst[n - 1] = '\0' yourself
strcat(dst, src)Append; overflows if there is no room
strchr(s, 'c') / strstr(s, "sub")Find a char / substring, returns a pointer or NULL
strtol(s, &end, 10)String to long with error checking (atoi has none)
memcpy(dst, src, n) / memset(p, 0, n)Copy / fill n bytes

Pointers #

CodeWhat it does
int *p = &x;p holds the address of x
*p = 10;Change x through the pointer (dereference)
int *p = NULL;Points to nothing; check before dereferencing (nullptr in C23)
p + 1Next element (moves by sizeof *p bytes)
p[i]Same as *(p + i)
q - pNumber of elements between two pointers (ptrdiff_t, print with %td)
int **pp = &p;Pointer to a pointer; **pp is x
const int *p / int *const pCan't change the value / can't change where p points
void *Generic pointer (returned by malloc); converts to any object pointer
p->fieldMember of the struct p points to, same as (*p).field
int (*op)(int, int) = add;Function pointer; call it with op(2, 3)
qsort(arr, n, sizeof arr[0], cmp)Library sort that takes a comparison function pointer

Structs, Unions & Enums #

CodeWhat it does
typedef struct { char name[32]; int age; } Person;Struct type you can use as just Person
Person p = {.name = "Ann", .age = 30};Designated initializers; missing fields become 0
p.age / ptr->ageAccess a field directly / through a pointer
struct Node { int value; struct Node *next; };Self-referencing struct (needs the tag name)
Person a = b;Copies all fields (but == doesn't work on structs)
union Value { int i; float f; };All members share the same memory; size of the largest
enum Color { RED, GREEN, BLUE };Named integer constants 0, 1, 2
enum { LOW = 5, HIGH };Set values explicitly; HIGH becomes 6
enum Color : uint8_t { ... };Fixed underlying type (C23)

Dynamic Memory #

CodeWhat it does
int *a = malloc(n * sizeof *a);Allocate n ints, uninitialized (<stdlib.h>)
if (a == NULL) { ... }Always check: allocation can fail
int *a = calloc(n, sizeof *a);Allocate n ints set to zero
int *tmp = realloc(a, m * sizeof *a);Resize; may move the block. Then if (tmp) a = tmp;
a = realloc(a, ...)Leaks the old block if realloc fails and returns NULL
free(a); a = NULL;Release memory and avoid a dangling pointer (free(NULL) is safe)
Memory leakForgetting free; find it with valgrind ./app or -fsanitize=address
Use after free / double freeTouching or freeing memory again after free: undefined behaviour

File I/O #

FILE *fp = fopen("data.txt", "r");
if (fp == NULL) { perror("data.txt"); return 1; }
char line[256];
while (fgets(line, sizeof line, fp)) {      // NULL at end of file
    line[strcspn(line, "\n")] = '\0';        // strip the newline fgets keeps
    printf("%s\n", line);
}
fclose(fp);
CodeWhat it does
"r" / "w" / "a"Read / write (creates or empties the file) / append
"r+" / "rb" / "wb"Read and write / binary mode (matters on Windows)
fprintf(fp, "%d\n", x)Formatted write to a file (stderr for error messages)
fputs("text\n", fp) / fgetc(fp)Write a string / read one char (EOF at the end)
fread(buf, sizeof buf[0], n, fp)Read n items of binary data (fwrite to write)
perror("msg")Print msg plus the reason for the last error
fclose(fp)Close and flush; always do it

Preprocessor #

CodeWhat it does
#include <stdio.h> / #include "util.h"Insert a system header / your own header
#define MAX 100Text replacement constant (no =, no ;)
#define SQUARE(x) ((x) * (x))Function-like macro; wrap every argument and the whole body in parentheses
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))Classic element-count macro
#ifdef DEBUG ... #endifCompile code only when DEBUG is defined (gcc -DDEBUG)
#ifndef UTIL_H / #define UTIL_H / #endifInclude guard: the header is processed only once
#pragma onceShorter include guard (non-standard but supported everywhere)
__FILE__ __LINE__ __func__Current file, line, function name (for log messages)
static_assert(sizeof(int) == 4, "need 32-bit int");Compile-time check (C11; message optional in C23)

Undefined Behaviour & Pitfalls #

MistakeWhat goes wrong
if (x = 5)Assigns instead of comparing; use == (-Wall warns)
int x; printf("%d", x);Reading an uninitialized local variable gives garbage
a[5] on int a[5]Out of bounds: valid indexes are 0 to 4
INT_MAX + 1Signed overflow is undefined; unsigned wraps around
i = i++ + 1;Modifying a variable twice in one expression
char *s = "hi"; s[0] = 'H';Writing to a string literal crashes or misbehaves
scanf("%d", n)Missing &: scanf writes to a random address
gets(buf)Removed in C11; use fgets(buf, sizeof buf, stdin)
sizeof arr on a parameterArray parameters are pointers: pass the length separately
return local_array;Pointer to a local variable is invalid once the function returns
#define DOUBLE(x) x * 2DOUBLE(1 + 2) is 1 + 2 * 2 = 5; add parentheses

Tips #

  • Always compile with -Wall -Wextra and fix every warning; most beginner bugs show up there first.
  • During development add -g -fsanitize=address,undefined: it turns silent memory bugs into clear error reports with line numbers.
  • Pass the buffer size with every buffer (fgets(buf, sizeof buf, fp), snprintf(buf, sizeof buf, ...)) and prefer them over gets, strcpy and sprintf.
  • Every malloc needs exactly one free, every fopen one fclose; decide which function owns the memory.
  • Use <stdint.h> types like uint8_t and uint32_t when the exact size matters (file formats, embedded registers, network data).