Skip to content

Reference21 min read

C Quick Reference

Look things up here; learn them in Part 1. Sizes are given for the three targets you will meet most: arm-none-eabi (Cortex-M, the course target), 64-bit Linux and macOS (LP64), and 64-bit Windows (LLP64).

TypeCortex-M (arm-none-eabi)Linux/macOS x86-64 and arm64Windows x64Guaranteed by C
char8 bits, unsigned8 bits, signed (x86-64), unsigned (arm64 Linux), signed (arm64 macOS)8 bits, signedat least 8 bits, signedness implementation-defined
short161616at least 16
int323232at least 16
long326432at least 32
long long646464at least 64
pointer, size_t, uintptr_t326464none
float32 (IEEE 754 single, hardware FPU on M4F)3232none (IEEE in practice)
double64 (software emulated on M4F)6464at least as precise as float
long double64 (same as double)80-bit x87 in 16 bytes (x86-64), 128-bit (arm64 Linux), 64 (arm64 macOS)64at least as precise as double
bool / _Bool888holds 0 or 1
enum32 unless -fshort-enums3232compatible with some integer type
wchar_t32, unsigned32, signed16, unsignedimplementation-defined
TypeMinMaxMacro
int8_t-128127INT8_MIN, INT8_MAX
uint8_t0255UINT8_MAX
int16_t-32,76832,767INT16_MIN, INT16_MAX
uint16_t065,535UINT16_MAX
int32_t-2,147,483,6482,147,483,647INT32_MIN, INT32_MAX
uint32_t04,294,967,295UINT32_MAX
int64_t-9,223,372,036,854,775,8089,223,372,036,854,775,807INT64_MIN, INT64_MAX
uint64_t018,446,744,073,709,551,615UINT64_MAX
size_t (Cortex-M)04,294,967,295SIZE_MAX
float propertyValue
Layout1 sign, 8 exponent (bias 127), 23 fraction bits
Largest finiteabout 3.4e38 (FLT_MAX)
Smallest normalabout 1.18e-38 (FLT_MIN)
Epsilonabout 1.19e-7 (FLT_EPSILON), roughly 7 decimal digits
Integers exactly representableall up to 2^24 = 16,777,216
double1 sign, 11 exponent (bias 1023), 52 fraction; about 15 to 16 digits; exact integers to 2^53
TypeMeaningUse for
uint8_t … uint64_t, int8_t … int64_tExact widthRegisters, wire formats, anything where size matters
uint_least16_t etc.Smallest type with at least N bitsPortable storage when exact width may not exist
uint_fast16_t etc.Fastest type with at least N bits (32 bits on Cortex-M)Loop counters, local arithmetic
intmax_t, uintmax_tWidest integer typePrinting any integer with %jd / %ju
intptr_t, uintptr_tInteger that can hold a pointerAddress arithmetic, alignment checks
size_tUnsigned result of sizeofSizes, counts, indexes
ptrdiff_tSigned difference of two pointersPointer subtraction
NULLNull pointer constantPointer initialisation (C23: prefer nullptr)
offsetof(type, member)Byte offset of a struct memberLayout checks, container_of

On arm-none-eabi with newlib, uint32_t is unsigned long and int32_t is long, not unsigned int and int. That is legal and it means printf("%u", x) with a uint32_t triggers a -Wformat warning. Use the PRIu32 macros below.

FormExampleType / meaning
Decimal42first of int, long, long long that fits
Hex0x2Afirst of int, unsigned int, long, unsigned long, … that fits
Octal052same rules as hex; a leading zero means octal
Binary0b101010C23 (GNU extension before)
Digit separator1'000'000C23
u / U1uunsigned
l / L1Llong
ul, ull1ullunsigned long, unsigned long long
f / F0.5ffloat (without it, 0.5 is double)
UINT32_C(x)UINT32_C(1)constant of type uint_least32_t
Character'A'type int in C (value 65)
String"abc"char[4] including the NUL, must not be modified
Escapes\n \r \t \0 \\ \' \" \xHH \ooonewline, CR, tab, NUL, backslash, quote, hex, octal

Highest first. Operators in the same row have equal precedence.

LevelOperatorsAssociativityNotes
1a() a[i] . -> a++ a-- (type){list} (compound literal)left to rightpostfix
2++a --a +a -a ! ~ (type) *p &x sizeof _Alignofright to leftunary, casts
3* / %left to right
4+ -left to right
5<< >>left to rightlower than +: 1 << n + 1 is 1 << (n + 1)
6< <= > >=left to right
7== !=left to right
8&left to rightlower than ==: a & b == c is a & (b == c)
9^left to right
10|left to right
11&&left to rightshort-circuits, sequence point
12||left to rightshort-circuits, sequence point
13?:right to left
14= += -= *= /= %= <<= >>= &= ^= |=right to left
15,left to rightsequence point

Traps worth memorising:

ExpressionParsed asUsually meant
*p++*(p++)read *p, then advance p (this one is usually intended)
(*p)++increment the value pointed to
a & b == ca & (b == c)(a & b) == c
!x & y(!x) & y!(x & y)
x << 2 + 1x << 3(x << 2) + 1
reg & MASK != 0reg & (MASK != 0)(reg & MASK) != 0
*p.f*(p.f)(*p).f, written p->f
int *a, b;b is int, not a pointerone declaration per line
RuleEffectExample
Integer promotionAnything narrower than int becomes int before arithmeticuint8_t a = 200, b = 100; a + b is int 300
Usual arithmetic conversionsBoth operands converted to a common type; if one is unsigned and at least as wide, the signed one becomes unsigned-1 < 1u is false (-1 becomes 0xFFFFFFFF)
Assignment to narrower typeUnsigned: wraps modulo 2^N. Signed: implementation-defined if out of rangeuint8_t x = 300; stores 44
~ on small unsignedPromoted to int first(uint8_t)~0x0F needed to get 0xF0 as 8 bits
Float to integerTruncates toward zero; UB if out of range(int)-2.7f is -2
Signed / and %Quotient truncates toward zero; remainder has sign of dividend-7 / 2 == -3, -7 % 2 == -1

The right-left rule: start at the identifier, read right until you hit a ) or the end, then read left until you hit a ( or the start, then step out one level and repeat. [] reads “array of”, () reads “function returning”, * reads “pointer to”.

DeclarationReads as
int *p;p is a pointer to int
int *p[8];p is an array of 8 pointers to int
int (*p)[8];p is a pointer to an array of 8 int
int *f(void);f is a function returning a pointer to int
int (*f)(void);f is a pointer to a function returning int
int (*tbl[4])(int);tbl is an array of 4 pointers to functions taking int returning int
const char *s;s is a pointer to const char (cannot modify the chars, can repoint s)
char *const s;s is a const pointer to char (can modify the chars, cannot repoint)
const char *const s;const pointer to const char
volatile uint32_t *const reg;const pointer to a volatile uint32_t (the register-access idiom)
void (*signal(int sig, void (*h)(int)))(int);signal is a function taking int and a handler pointer, returning a pointer to a function taking int returning void
typedef void (*isr_t)(void);isr_t names “pointer to function taking nothing returning void”

const applies to the thing immediately to its left, or to the right if nothing is on its left.

SpecifierArgument typeExample output
%d, %iint-42
%uunsigned int42
%x, %Xunsigned int, hex2a, 2A
%ounsigned int, octal52
%cint (as character)A
%schar * (NUL-terminated)hello
%pvoid *0x20000010 (format implementation-defined)
%fdouble (float is promoted)3.140000
%e, %g, %adouble3.140000e+00, 3.14, 0x1.91eb851eb851fp+1
%%none%
Length modifierApplies toExample
hhchar sized%hhu for unsigned char
hshort%hd
llong%ld, %lu, %lx
lllong long%lld, %llu
jintmax_t%jd
zsize_t%zu
tptrdiff_t%td
Llong double%Lf
Flag / fieldMeaningExampleOutput
widthMinimum field width%5d with 42 42
0Pad with zeros%08lx with 0x1234UL00001234
-Left-justify%-5d with 4242
+Always show sign%+d with 42+42
#Alternate form%#x with 420x2a
.NPrecision%.2f with 3.141593.14
*Width from argument%*d with 5, 42 42

On newlib-nano, %f prints nothing unless you link with -u _printf_float.

These expand to string literals that you paste into a format string.

TypePrint decimalPrint hexScan
int8_t / uint8_tPRId8 / PRIu8PRIx8SCNd8 / SCNu8
int16_t / uint16_tPRId16 / PRIu16PRIx16SCNd16 / SCNu16
int32_t / uint32_tPRId32 / PRIu32PRIx32, PRIX32SCNd32 / SCNu32
int64_t / uint64_tPRId64 / PRIu64PRIx64SCNd64 / SCNu64
intptr_t / uintptr_tPRIdPTR / PRIuPTRPRIxPTRSCNxPTR
intmax_t / uintmax_tPRIdMAX / PRIuMAXPRIxMAXSCNdMAX
#include <inttypes.h>
#include <stdio.h>
void show(uint32_t addr, uint32_t value)
{
printf("0x%08" PRIX32 " = %" PRIu32 "\n", addr, value);
}
KeywordAt file scopeAt block scope
(none)External linkage, static lifetime, zero-initialisedAutomatic: lives on the stack or in a register, indeterminate until assigned
staticInternal linkage (visible only in this file)Static lifetime: one instance, persists between calls, zero-initialised
externDeclares an object or function defined elsewhereSame, refers to a file-scope definition
autoNot allowedDefault, redundant (C23 reuses it for type inference)
registerNot allowedHint only; cannot take the address. Historical
_Thread_local / thread_localOne instance per threadMust be combined with static or extern
typedefGrouped with storage classes by the grammar; declares a type nameSame
QualifierMeaningFirmware use
constObject must not be modified through this nameTables in flash, read-only parameters
volatileEvery access is a side effect: not removed, merged, or reordered relative to other volatile accessesHardware registers, variables shared with ISRs. Not atomic, not a barrier
restrictPromise that this pointer is the only way to reach the object in this scopeLetting the compiler optimise copy and DSP loops
_AtomicAccesses are atomic with defined memory orderingLock-free flags and counters (stdatomic.h)
Function specifierMeaning
inlineHint to inline; affects linkage rules. Use static inline in headers
_Noreturn / [[noreturn]]Function never returns (reset, fatal error handler)
DirectivePurpose
#include <file>Search the -I directories, then the system include paths
#include "file"Search the current file’s directory first, then the same paths as <file>
#define NAME valueObject-like macro
#define F(a, b) ((a) + (b))Function-like macro: parenthesise every parameter and the whole body
#define LOG(...) printf(__VA_ARGS__)Variadic macro
#undef NAMERemove a macro
#if expr / #elif / #else / #endifConditional compilation on integer constant expressions
#ifdef NAME / #ifndef NAMEShorthand for #if defined(NAME) / #if !defined(NAME)
#elifdef / #elifndefC23 shorthands
#error "msg"Stop compilation with a message
#warning "msg"Warn and continue (C23; GNU extension before)
#pragma onceNon-standard but universal include guard
#pragma GCC diagnostic push/pop/ignored "-Wxxx"Locally change warnings
_Pragma("...")Pragma usable inside a macro
#line N "file"Change reported line and file (generated code)
#embed "file"C23: include a binary file as initialiser bytes
__has_include(<file>)C23 (GNU extension before): test whether a header exists
#xStringify a macro parameter
a ## bPaste two tokens
__VA_OPT__(,)C23: emit the content only if variadic arguments are present
Predefined macroValue
__FILE__, __LINE__Current file name and line number
__func__Current function name (a predefined identifier, not a macro)
__DATE__, __TIME__Build date and time (breaks reproducible builds)
__STDC_VERSION__201112L (C11), 201710L (C17), 202311L (C23)
__STDC_HOSTED__1 hosted, 0 freestanding
__GNUC__, __clang__Compiler identification
__ARM_ARCH, __ARM_ARCH_7EM__Arm architecture (7 and ARMv7E-M for Cortex-M4)
__ARM_FPDefined when hardware floating point is available
__thumb__, __thumb2__Compiling Thumb code
__BYTE_ORDER__Compare with __ORDER_LITTLE_ENDIAN__ (GCC, Clang)
StandardKeywords added
C89/C90auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while
C99inline restrict _Bool _Complex _Imaginary
C11_Alignas _Alignof _Atomic _Generic _Noreturn _Static_assert _Thread_local
C17none (defect fixes only)
C23alignas alignof bool true false constexpr nullptr static_assert thread_local typeof typeof_unqual _BitInt _Decimal32 _Decimal64 _Decimal128
HeaderGives you (before C23)
stdbool.hbool, true, false
stdalign.halignas, alignof
stdnoreturn.hnoreturn
assert.hassert, and static_assert in C11
stdatomic.hatomic_int, atomic_load, atomic_store, atomic_fetch_add, memory orders

Undefined behaviour that matters in firmware

Section titled “Undefined behaviour that matters in firmware”

The compiler assumes none of these happen and optimises accordingly. Any of them can make code behave differently at -O2 than at -O0.

#Undefined behaviourExampleSafe alternative
1Signed integer overflowint32_t x = INT32_MAX; x + 1Unsigned arithmetic, or check before adding
2Shift by negative or by at least the width1u << 32Check the shift count
3Left shift into or of the sign bit of a signed type1 << 31 with 32-bit int1u << 31
4Integer division or modulo by zerox / 0Check the divisor
5INT_MIN / -1 or INT_MIN % -1INT32_MIN / -1Check for this case
6Array access out of boundsbuf[16] on uint8_t buf[16]Bounds check, ARRAY_SIZE
7Pointer arithmetic beyond one past the endp = buf + 17Keep pointers within the object
8Dereferencing a null pointer*(uint32_t *)0 (on STM32 this reads flash and does not fault)Check pointers; do not rely on a fault
9Reading an uninitialised automatic variableuint32_t x; return x;Initialise at declaration
10Use after free, or after the object’s lifetime endedReturning &localStatic or caller-provided storage
11Modifying a string literalchar *s = "abc"; s[0] = 'x';char s[] = "abc";
12Modifying an object defined constCasting away const from a flash table and writingDo not; on MCU the write faults or is ignored
13Strict aliasing violation*(uint32_t *)&my_floatmemcpy or a union
14Misaligned access through a typed pointer*(uint32_t *)(buf + 1)memcpy, or assemble bytes with shifts
15Unsequenced modificationi = i++;, a[i] = i++;One side effect per variable per expression
16Data race between threads (C11)Two tasks write a non-atomic variableAtomics or a mutex; for ISRs use critical sections
17Calling a function through an incompatible pointer typeCalling void f(int) via void (*)(void)Matching prototypes and typedefs
18Using the value of a non-void function that fell off its endMissing return on one path-Wreturn-type, return on every path
19Overlapping source and destination in memcpymemcpy(buf + 1, buf, n)memmove
20Float to integer conversion out of range(uint8_t)300.0f, (int32_t)1e10fClamp before converting
21Returning from a _Noreturn functionA noreturn panic handler that returnsEnd with an infinite loop or reset
22Side-effect-free loop with a non-constant condition (strictly not UB in C: C11 6.8.5p6 lets the compiler assume it terminates)while (flag) ; with non-volatile flagvolatile or atomic flag
FlagCatches
-WallThe core set: unused variables, missing returns, format mismatches, uninitialised use, and more
-WextraUnused parameters, signed/unsigned comparison, empty bodies, missing initialisers
-WpedanticNon-ISO extensions
-WerrorTurns warnings into errors (use in CI)
-WshadowA local hides another variable
-WconversionImplicit conversions that can change a value
-Wsign-conversionImplicit signed/unsigned conversions (included in -Wconversion for C)
-Wsign-compareComparing signed with unsigned (in -Wextra)
-Wdouble-promotionfloat silently promoted to double (slow on M4F)
-Wformat=2Format string problems, including non-literal format strings
-WundefUndefined macro used in #if (evaluates as 0 silently otherwise)
-Wstrict-prototypesFunction declared without a parameter list, f() instead of f(void)
-Wmissing-prototypesNon-static function defined with no prior prototype (should it be static?)
-Wcast-alignPointer casts that increase alignment (=strict warns even on targets that allow it)
-Wcast-qualCasting away const or volatile
-Wpointer-arithArithmetic on void * or function pointers
-Wswitch-enumswitch on an enum missing a case, even with a default
-Wimplicit-fallthroughcase falls through without a marker (in -Wextra)
-WvlaVariable-length arrays
-Wstack-usage=NFunctions using more than N bytes of stack
-Wnull-dereferencePaths that dereference null (needs optimisation on)
-Wlogical-opSuspicious logical operations, such as x == 1 && x == 2 (GCC)
-Wduplicated-cond, -Wduplicated-branchesRepeated conditions or identical branches (GCC)
-Wstrict-aliasing=2More aggressive strict-aliasing warnings

A good default set for this course:

WARN := -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wdouble-promotion \
-Wformat=2 -Wundef -Wstrict-prototypes -Wmissing-prototypes \
-Wcast-align -Wvla