Short answer: Compile with gcc -std=c17 -Wall -Wextra -O2 -o app app.c (or -std=c23 on GCC 14+ and Clang 18+). Use fixed-width types from <stdint.h>, size_t for sizes, snprintf instead of sprintf, and check every malloc, fopen and strtol. C23 adds bool, nullptr, constexpr, typeof and auto as keywords. Build test binaries with -g -fsanitize=address,undefined to catch memory bugs early.
The examples in this C cheat sheet were run with GCC 13.3, GCC 14.2 and Clang 18.1 on Ubuntu 24.04 on 9 October 2026. Notes about GCC 15 and 16 defaults and #embed were checked against the official documentation.
Table of Contents
Compile and run with gcc and clang
| Command | What it does |
|---|---|
gcc -std=c17 -Wall -Wextra -O2 -o app app.c | Build one file with the usual warnings and optimisation |
gcc -std=c23 ... / clang -std=c23 ... | C23 mode. GCC 13 only accepts the old name -std=c2x |
-Wpedantic | Warn about GNU extensions that are not ISO C |
-Werror | Turn warnings into errors (good in CI, risky in release tarballs) |
-g -O0 | Debug build for gdb |
-g -fsanitize=address,undefined | Catch buffer overflows, use-after-free and undefined behaviour at run time |
-c util.c | Compile to util.o without linking |
-I include -L lib -lssl | Header path, library path, link libssl |
-lm | Link the maths library (needed for sqrt, pow on glibc) |
-DDEBUG=1 | Define a macro from the command line |
-E / -S | Stop after preprocessing / after generating assembly |
$(pkg-config --cflags --libs libcurl) | Flags for an installed library |
GCC 15 changed the default C mode from gnu17 to gnu23, so old code that uses bool, true or false as identifiers can stop compiling. Pin the standard with -std= in every build. __STDC_VERSION__ is 201710L for C17 and 202311L for C23 (GCC 13 and 14 still report the draft value 202000L).
Types, variables and C23 additions
| Syntax | What it does | Example |
|---|---|---|
int, long, char, double | Basic types; sizes depend on the platform | long n = 10L; |
int32_t, uint64_t | Fixed-width integers from <stdint.h> | uint16_t port = 443; |
size_t | Unsigned type for sizes and indexes; print with %zu | size_t n = strlen(s); |
const | Read-only through this name | const char *msg = "ok"; |
static | File-local function, or a local that keeps its value | static int calls; |
bool (C23 keyword) | Needs <stdbool.h> before C23 | bool ok = true; |
nullptr (C23) | Typed null pointer constant | int *p = nullptr; |
constexpr (C23) | Compile-time constant object (GCC 13+, Clang 19+) | constexpr int MAX = 100; |
auto (C23) | Infer the type from the initialiser | auto n = 42; |
typeof (C23) | Type of an expression | typeof(n) m = 0; |
0b1010, 1'000'000 | C23 binary literals and digit separators | long big = 1'000'000; |
static_assert(expr) | Compile-time check; C23 makes the message optional | static_assert(sizeof(int) == 4); |
[[nodiscard]] | C23 attribute: warn if the return value is ignored | [[nodiscard]] int f(void); |
#include <stdio.h>
#include <stdint.h>
constexpr int MAX_USERS = 100; // C23
static_assert(sizeof(int32_t) == 4); // C23: message optional
int main(void) {
bool ok = true; // no <stdbool.h> needed in C23
int *p = nullptr;
auto n = 42;
typeof(n) m = 0b1010;
printf("%d %d %d %d %ld\n", ok, p == nullptr, n + MAX_USERS, m, __STDC_VERSION__);
}
Pointers, arrays and strings
| Syntax | Meaning |
|---|---|
int *p = &x; | p holds the address of x |
*p = 5; | Write through the pointer |
p[i] is *(p + i) | Indexing is pointer arithmetic |
int a[10]; | Array; decays to int * when passed to a function, so pass the length too |
sizeof a / sizeof a[0] | Element count. Works on the array, not on a pointer to it |
const char *s vs char *const s | Pointer to read-only chars vs read-only pointer |
strlen(s) | Length without the terminating \0 |
strcmp(a, b) == 0 | Equal strings. Never compare strings with == |
snprintf(buf, sizeof buf, ...) | Safe formatting; returns the length it wanted to write |
memcpy(dst, src, n) / memmove | Copy bytes; use memmove when the areas overlap |
strtol(s, &end, 10) | Parse an integer with error checking (atoi has none) |
char buf[16];
int need = snprintf(buf, sizeof buf, "host-%s.example", "web01");
if (need >= (int)sizeof buf)
fprintf(stderr, "truncated, needed %d bytes\n", need);
char *end;
errno = 0;
long port = strtol(arg, &end, 10);
if (errno || *end != '\0' || port < 1 || port > 65535)
fprintf(stderr, "bad port: %s\n", arg);
GCC also caught the truncation at compile time with -Wformat-truncation, which -Wall enables. Avoid strcpy, strcat, sprintf and gets (removed in C11) in new code.
Structs, enums, unions and function pointers
typedef struct {
char name[32];
uint16_t port;
bool tls;
} Service;
static int by_port(const void *a, const void *b) {
const Service *x = a, *y = b;
return (x->port > y->port) - (x->port < y->port);
}
Service svcs[] = {
{ .name = "https", .port = 443, .tls = true }, // designated initialisers
{ .name = "ssh", .port = 22 }, // other fields become 0
};
qsort(svcs, sizeof svcs / sizeof svcs[0], sizeof svcs[0], by_port);
| Syntax | What it does |
|---|---|
s.port / ps->port | Member access on a struct / through a pointer |
enum level { LOW, MEDIUM = 5, HIGH }; | Named integer constants (HIGH is 6) |
union value { int i; float f; }; | Members share the same memory; read the one you last wrote |
typedef int (*cmp_fn)(const void *, const void *); | Name for a function pointer type |
cmp_fn f = by_port; f(a, b); | Call through a function pointer |
Memory management: malloc, realloc and free
| Call | Notes |
|---|---|
T *p = malloc(n * sizeof *p); | Uninitialised memory. Check for NULL |
calloc(n, sizeof *p) | Zeroed memory, with an overflow check on n * size |
realloc(p, new_size) | Grow or shrink. Assign to a temporary first |
free(p); p = NULL; | Release, then clear the pointer to avoid a double free |
sizeof *p | Size of what p points to; stays right if the type changes |
int *tmp = realloc(arr, cap * 2 * sizeof *arr);
if (!tmp) { /* arr is still valid here */
free(arr);
return -1;
}
arr = tmp;
cap *= 2;
Writing arr = realloc(arr, ...) leaks the old block when realloc fails. Every malloc needs exactly one free. ASan reports a heap overflow like this:
$ gcc -g -fsanitize=address -o bug bug.c && ./bug
==9104==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000020 ...
WRITE of size 4 at 0x502000000020 thread T0
#0 ... in main bug.c:2
Files, errors and a log-parsing example
| Call | What it does |
|---|---|
FILE *fp = fopen(path, "r"); | Open for reading ("w" truncates, "a" appends) |
fgets(buf, sizeof buf, fp) | Read one line into a fixed buffer |
getline(&line, &cap, fp) | POSIX: read a line of any length; free line afterwards |
fprintf(stderr, ...) | Errors go to stderr, not stdout |
perror(path) / strerror(errno) | Print the reason a call failed |
fclose(fp) | Flush and close; check its return value when writing |
return EXIT_FAILURE; | Non-zero exit status for scripts and systemd |
For server admins: a small, fast counter for failed SSH logins in an auth log.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: %s LOGFILE\n", argv[0]);
return EXIT_FAILURE;
}
FILE *fp = fopen(argv[1], "r");
if (!fp) { perror(argv[1]); return EXIT_FAILURE; }
char *line = NULL;
size_t cap = 0;
long hits = 0;
while (getline(&line, &cap, fp) != -1)
if (strstr(line, "Failed password"))
hits++;
free(line);
fclose(fp);
printf("%ld failed SSH logins\n", hits);
return EXIT_SUCCESS;
}
Preprocessor, headers and Makefiles
| Directive | What it does |
|---|---|
#include <stdio.h> / #include "util.h" | System header / project header |
#ifndef UTIL_H #define UTIL_H … #endif | Include guard (or #pragma once, widely supported but not ISO) |
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0])) | Function-like macro; bracket every argument |
#if __STDC_VERSION__ >= 202311L | Compile only in C23 mode |
#ifdef DEBUG / #ifndef DEBUG | Test whether a macro exists |
#embed "cert.pem" | C23: include a file’s bytes as data (GCC 15+, Clang 19+) |
CC = gcc
CFLAGS = -std=c17 -Wall -Wextra -O2
LDLIBS = -lm
OBJS = main.o util.o
app: $(OBJS)
$(CC) $(CFLAGS) -o $@ $(OBJS) $(LDLIBS)
%.o: %.c util.h
$(CC) $(CFLAGS) -c $<
clean:
rm -f app $(OBJS)
.PHONY: clean
Recipe lines must start with a tab. Override variables per run: make CC=clang CFLAGS="-std=c23 -DDEBUG=1".
Debugging and common undefined behaviour
| Tool or bug | What to do |
|---|---|
gdb ./app then run, bt, print x | Backtrace after a crash; build with -g -O0 |
valgrind --leak-check=full ./app | Leaks and invalid reads without recompiling (slower than ASan) |
ulimit -c unlimited | Allow core dumps; on systemd hosts read them with coredumpctl |
| Signed integer overflow | Undefined. Check limits before adding, or use unsigned types |
| Reading uninitialised variables | Initialise at declaration; -Wall warns about many cases |
Use after free | Set the pointer to NULL; ASan reports heap-use-after-free |
| Off-by-one on arrays | Loop i < n, not i <= n; leave room for \0 |
| Format string mismatch | %zu for size_t, %ld for long, PRIu64 for uint64_t |
Sanitizers and valgrind are for test builds. Do not ship ASan binaries to production servers: they use much more memory and change error handling.
Official documentation (checked 9 October 2026): GCC: C Dialect Options · GCC: Warning Options · GCC: Instrumentation Options (sanitizers) · GCC 15 Changes (C23 by default) · Clang C Language Status · WG14 N3220 (C23 working draft) · The GNU C Library Manual
C Cheat Sheet at a glance


Related guides: C++ Cheat Sheet: C++20 and C++23 Syntax, STL and Ranges · Rust Cheat Sheet: Cargo, Ownership, Enums and Errors · Linux Commands Cheat Sheet for Server Admins.
Reading a lot of our guides? Go ad-free with srvScripts Pro for $6 a month. It also pays for new guides. Cancel any time.