Emergency server help: get in touch

C Cheat Sheet: C17 and C23 Syntax, Memory and gcc Flags

A compact C17 and C23 reference covering compiler flags, types, pointers, memory, file I/O, the preprocessor and debugging.

Published 8 min read

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.

Compile and run with gcc and clang

CommandWhat it does
gcc -std=c17 -Wall -Wextra -O2 -o app app.cBuild 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
-WpedanticWarn about GNU extensions that are not ISO C
-WerrorTurn warnings into errors (good in CI, risky in release tarballs)
-g -O0Debug build for gdb
-g -fsanitize=address,undefinedCatch buffer overflows, use-after-free and undefined behaviour at run time
-c util.cCompile to util.o without linking
-I include -L lib -lsslHeader path, library path, link libssl
-lmLink the maths library (needed for sqrt, pow on glibc)
-DDEBUG=1Define a macro from the command line
-E / -SStop 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

SyntaxWhat it doesExample
int, long, char, doubleBasic types; sizes depend on the platformlong n = 10L;
int32_t, uint64_tFixed-width integers from <stdint.h>uint16_t port = 443;
size_tUnsigned type for sizes and indexes; print with %zusize_t n = strlen(s);
constRead-only through this nameconst char *msg = "ok";
staticFile-local function, or a local that keeps its valuestatic int calls;
bool (C23 keyword)Needs <stdbool.h> before C23bool ok = true;
nullptr (C23)Typed null pointer constantint *p = nullptr;
constexpr (C23)Compile-time constant object (GCC 13+, Clang 19+)constexpr int MAX = 100;
auto (C23)Infer the type from the initialiserauto n = 42;
typeof (C23)Type of an expressiontypeof(n) m = 0;
0b1010, 1'000'000C23 binary literals and digit separatorslong big = 1'000'000;
static_assert(expr)Compile-time check; C23 makes the message optionalstatic_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

SyntaxMeaning
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 sPointer to read-only chars vs read-only pointer
strlen(s)Length without the terminating \0
strcmp(a, b) == 0Equal strings. Never compare strings with ==
snprintf(buf, sizeof buf, ...)Safe formatting; returns the length it wanted to write
memcpy(dst, src, n) / memmoveCopy 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);
SyntaxWhat it does
s.port / ps->portMember 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

CallNotes
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 *pSize 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

CallWhat 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

DirectiveWhat it does
#include <stdio.h> / #include "util.h"System header / project header
#ifndef UTIL_H #define UTIL_H … #endifInclude 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__ >= 202311LCompile only in C23 mode
#ifdef DEBUG / #ifndef DEBUGTest 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 bugWhat to do
gdb ./app then run, bt, print xBacktrace after a crash; build with -g -O0
valgrind --leak-check=full ./appLeaks and invalid reads without recompiling (slower than ASan)
ulimit -c unlimitedAllow core dumps; on systemd hosts read them with coredumpctl
Signed integer overflowUndefined. Check limits before adding, or use unsigned types
Reading uninitialised variablesInitialise at declaration; -Wall warns about many cases
Use after freeSet the pointer to NULL; ASan reports heap-use-after-free
Off-by-one on arraysLoop 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

C Cheat Sheet – short answer
In short: A compact C17 and C23 reference covering compiler flags, types, pointers, memory, file I/O, the preprocessor and debugging.
C Cheat Sheet – what this guide covers
C Cheat Sheet: what this guide covers.

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.

Share this guide

Free website test

Is your website set up right?

Check SSL, security headers, redirects, robots.txt, sitemap, llms.txt and security.txt in one test. It takes about 30 seconds.