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).
Type Cortex-M (arm-none-eabi) Linux/macOS x86-64 and arm64 Windows x64 Guaranteed by C char8 bits, unsigned 8 bits, signed (x86-64), unsigned (arm64 Linux), signed (arm64 macOS) 8 bits, signed at least 8 bits, signedness implementation-defined short16 16 16 at least 16 int32 32 32 at least 16 long32 64 32 at least 32 long long64 64 64 at least 64 pointer, size_t, uintptr_t 32 64 64 none float32 (IEEE 754 single, hardware FPU on M4F) 32 32 none (IEEE in practice) double64 (software emulated on M4F) 64 64 at 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) 64 at least as precise as double bool / _Bool8 8 8 holds 0 or 1 enum32 unless -fshort-enums 32 32 compatible with some integer type wchar_t32, unsigned 32, signed 16, unsigned implementation-defined
Type Min Max Macro int8_t-128 127 INT8_MIN, INT8_MAXuint8_t0 255 UINT8_MAXint16_t-32,768 32,767 INT16_MIN, INT16_MAXuint16_t0 65,535 UINT16_MAXint32_t-2,147,483,648 2,147,483,647 INT32_MIN, INT32_MAXuint32_t0 4,294,967,295 UINT32_MAXint64_t-9,223,372,036,854,775,808 9,223,372,036,854,775,807 INT64_MIN, INT64_MAXuint64_t0 18,446,744,073,709,551,615 UINT64_MAXsize_t (Cortex-M)0 4,294,967,295 SIZE_MAX
float propertyValue Layout 1 sign, 8 exponent (bias 127), 23 fraction bits Largest finite about 3.4e38 (FLT_MAX) Smallest normal about 1.18e-38 (FLT_MIN) Epsilon about 1.19e-7 (FLT_EPSILON), roughly 7 decimal digits Integers exactly representable all 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
Type Meaning Use for uint8_t … uint64_t, int8_t … int64_tExact width Registers, wire formats, anything where size matters uint_least16_t etc.Smallest type with at least N bits Portable 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 type Printing any integer with %jd / %ju intptr_t, uintptr_tInteger that can hold a pointer Address arithmetic, alignment checks size_tUnsigned result of sizeof Sizes, counts, indexes ptrdiff_tSigned difference of two pointers Pointer subtraction NULLNull pointer constant Pointer initialisation (C23: prefer nullptr) offsetof(type, member)Byte offset of a struct member Layout 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.
Form Example Type / meaning Decimal 42first of int, long, long long that fits Hex 0x2Afirst of int, unsigned int, long, unsigned long, … that fits Octal 052same rules as hex; a leading zero means octal Binary 0b101010C23 (GNU extension before) Digit separator 1'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 modifiedEscapes \n \r \t \0 \\ \' \" \xHH \ooonewline, CR, tab, NUL, backslash, quote, hex, octal
Highest first. Operators in the same row have equal precedence.
Level Operators Associativity Notes 1 a() a[i] . -> a++ a-- (type){list} (compound literal)left to right postfix 2 ++a --a +a -a ! ~ (type) *p &x sizeof _Alignofright to left unary, casts 3 * / %left to right 4 + -left to right 5 << >>left to right lower than +: 1 << n + 1 is 1 << (n + 1) 6 < <= > >=left to right 7 == !=left to right 8 &left to right lower than ==: a & b == c is a & (b == c) 9 ^left to right 10 |left to right 11 &&left to right short-circuits, sequence point 12 ||left to right short-circuits, sequence point 13 ?:right to left 14 = += -= *= /= %= <<= >>= &= ^= |=right to left 15 ,left to right sequence point
Traps worth memorising:
Expression Parsed as Usually 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) + 1reg & MASK != 0reg & (MASK != 0)(reg & MASK) != 0*p.f*(p.f)(*p).f, written p->fint *a, b;b is int, not a pointerone declaration per line
Rule Effect Example Integer promotion Anything narrower than int becomes int before arithmetic uint8_t a = 200, b = 100; a + b is int 300Usual arithmetic conversions Both 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 type Unsigned: wraps modulo 2^N. Signed: implementation-defined if out of range uint8_t x = 300; stores 44~ on small unsignedPromoted to int first (uint8_t)~0x0F needed to get 0xF0 as 8 bitsFloat to integer Truncates toward zero; UB if out of range (int)-2.7f is -2Signed / 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”.
Declaration Reads as int *p;p is a pointer to intint *p[8];p is an array of 8 pointers to intint (*p)[8];p is a pointer to an array of 8 intint *f(void);f is a function returning a pointer to intint (*f)(void);f is a pointer to a function returning intint (*tbl[4])(int);tbl is an array of 4 pointers to functions taking int returning intconst 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 charvolatile 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 voidtypedef 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.
Specifier Argument type Example 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 modifier Applies to Example hhchar sized%hhu for unsigned charhshort%hdllong%ld, %lu, %lxlllong long%lld, %llujintmax_t%jdzsize_t%zutptrdiff_t%tdLlong double%Lf
Flag / field Meaning Example Output width Minimum field width %5d with 42 420Pad 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.
Type Print decimal Print hex Scan int8_t / uint8_tPRId8 / PRIu8PRIx8SCNd8 / SCNu8int16_t / uint16_tPRId16 / PRIu16PRIx16SCNd16 / SCNu16int32_t / uint32_tPRId32 / PRIu32PRIx32, PRIX32SCNd32 / SCNu32int64_t / uint64_tPRId64 / PRIu64PRIx64SCNd64 / SCNu64intptr_t / uintptr_tPRIdPTR / PRIuPTRPRIxPTRSCNxPTRintmax_t / uintmax_tPRIdMAX / PRIuMAXPRIxMAXSCNdMAX
void show ( uint32_t addr , uint32_t value )
printf ( "0x % 08" PRIX32 " = %" PRIu32 " \n " , addr, value);
Keyword At file scope At block scope (none) External linkage, static lifetime, zero-initialised Automatic: 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 elsewhere Same, refers to a file-scope definition autoNot allowed Default, redundant (C23 reuses it for type inference) registerNot allowed Hint only; cannot take the address. Historical _Thread_local / thread_localOne instance per thread Must be combined with static or extern typedefGrouped with storage classes by the grammar; declares a type name Same
Qualifier Meaning Firmware use constObject must not be modified through this name Tables in flash, read-only parameters volatileEvery access is a side effect: not removed, merged, or reordered relative to other volatile accesses Hardware registers, variables shared with ISRs. Not atomic, not a barrier restrictPromise that this pointer is the only way to reach the object in this scope Letting the compiler optimise copy and DSP loops _AtomicAccesses are atomic with defined memory ordering Lock-free flags and counters (stdatomic.h)
Function specifier Meaning inlineHint to inline; affects linkage rules. Use static inline in headers _Noreturn / [[noreturn]]Function never returns (reset, fatal error handler)
Directive Purpose #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 macro Value __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)
Standard Keywords added C89/C90 auto 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 whileC99 inline restrict _Bool _Complex _ImaginaryC11 _Alignas _Alignof _Atomic _Generic _Noreturn _Static_assert _Thread_localC17 none (defect fixes only) C23 alignas alignof bool true false constexpr nullptr static_assert thread_local typeof typeof_unqual _BitInt _Decimal32 _Decimal64 _Decimal128
Header Gives you (before C23) stdbool.hbool, true, falsestdalign.halignas, alignofstdnoreturn.hnoreturnassert.hassert, and static_assert in C11stdatomic.hatomic_int, atomic_load, atomic_store, atomic_fetch_add, memory orders
The compiler assumes none of these happen and optimises accordingly. Any of them can make code behave differently at -O2 than at -O0.
# Undefined behaviour Example Safe alternative 1 Signed integer overflow int32_t x = INT32_MAX; x + 1Unsigned arithmetic, or check before adding 2 Shift by negative or by at least the width 1u << 32Check the shift count 3 Left shift into or of the sign bit of a signed type 1 << 31 with 32-bit int1u << 314 Integer division or modulo by zero x / 0Check the divisor 5 INT_MIN / -1 or INT_MIN % -1INT32_MIN / -1Check for this case 6 Array access out of bounds buf[16] on uint8_t buf[16]Bounds check, ARRAY_SIZE 7 Pointer arithmetic beyond one past the end p = buf + 17Keep pointers within the object 8 Dereferencing a null pointer *(uint32_t *)0 (on STM32 this reads flash and does not fault)Check pointers; do not rely on a fault 9 Reading an uninitialised automatic variable uint32_t x; return x;Initialise at declaration 10 Use after free, or after the object’s lifetime ended Returning &local Static or caller-provided storage 11 Modifying a string literal char *s = "abc"; s[0] = 'x';char s[] = "abc";12 Modifying an object defined const Casting away const from a flash table and writing Do not; on MCU the write faults or is ignored 13 Strict aliasing violation *(uint32_t *)&my_floatmemcpy or a union14 Misaligned access through a typed pointer *(uint32_t *)(buf + 1)memcpy, or assemble bytes with shifts15 Unsequenced modification i = i++;, a[i] = i++;One side effect per variable per expression 16 Data race between threads (C11) Two tasks write a non-atomic variable Atomics or a mutex; for ISRs use critical sections 17 Calling a function through an incompatible pointer type Calling void f(int) via void (*)(void) Matching prototypes and typedefs 18 Using the value of a non-void function that fell off its end Missing return on one path -Wreturn-type, return on every path19 Overlapping source and destination in memcpy memcpy(buf + 1, buf, n)memmove20 Float to integer conversion out of range (uint8_t)300.0f, (int32_t)1e10fClamp before converting 21 Returning from a _Noreturn function A noreturn panic handler that returns End with an infinite loop or reset 22 Side-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
Flag Catches -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 \