Module 7 Exercises — Modern C After K&R

Back to the Course 3 syllabus. Read first: MC 1–38; Seacord 2–4 as assigned.

Work in course3/m7/ of the labs repo. Everything here runs on the Mac: host-side experiments compile with clang, and every STM32-flavored question is answered by cross-compiling with arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb and reading the disassembly — same convention as Module 6, no board required. Predicted cells are filled in before compiling; observed cells at the machine.

Exercises

Exercise 7.1 — The dialect ladder. Write one small source file that deliberately uses the Module 7 feature set: a // comment, a mid-block declaration, a for-scope counter, a designated initializer, a compound literal, a static inline helper, a variadic macro, and a _Static_assert. Compile it under -std=c90, -std=c99, -std=c11, -std=c17, and -std=gnu17, each with the MC 49 warning set (-Wall -Wextra -Wpedantic -Wconversion -Wshadow -Wstrict-prototypes), on both clang (host) and arm-none-eabi-gcc. Complete the acceptance table:

Feature c90 c99 c11 c17 gnu17
// comment
Mid-block declaration
Designated initializer
Compound literal
static inline
Variadic macro
_Static_assert

In notes.md: which mode the Course 2 labs repo toolchain files actually pin, and why gnu17 rather than strict c17 is the practical choice for code that includes CMSIS headers (MC 2).

Exercise 7.2 — Modernize a K&R-era module. Write (or resurrect) a small module in deliberately 1989-vintage style: all declarations at the top, plain int/long everywhere, memset for clearing state, function-like macros for small helpers, positional struct initializers. Then port it to the Module 7 idiom: <stdint.h>/<stdbool.h> types, declarations at first use, {0} and designated initializers, static inline replacing the macros, _Static_assert guarding every layout or width assumption the old code made silently. Diff the -O2 disassembly of both versions on both toolchains. Deliverable: the before/after source pair plus a notes.md verdict on the codegen diff — the modernization should be free (identical or better code), and the note says where and why.

Exercise 7.3 — Widths and promotions, LP64 vs. ILP32. The same C integer expressions mean subtly different things on the Mac (LP64) and the STM32 (ILP32). First fill in the sizes table by prediction, then verify — on the host by running, on the cross side with _Static_assert(sizeof(...) == ...) probes that either compile or don’t:

Type Apple Silicon (LP64) Cortex-M4 (ILP32)
int
long
size_t
uintptr_t
long long

Then predict the value and type of a battery of promotion traps from MC 6–10 and Seacord 3 — uint8_t * uint8_t overflowing into int, uint16_t shifted left 20, int8_t right-shifted, unsigned wraparound, a signed/unsigned comparison, a 32×32→64 product with and without the cast-before-multiply — and verify on the host with <inttypes.h> PRI macros. Record every case where your prediction missed and which promotion rule explains it.

Exercise 7.4 — Designated initializers and the compound-literal lifetime. Build a CMSIS-style configuration struct (model it on GPIO_InitTypeDef) and a sparse lookup table using [index] = designators. Verify in lldb that unnamed members and gaps really are zero-filled (MC 12, 14). Then demonstrate the MC 13 pitfall deliberately: a function that returns (or stores) a pointer to a block-scope compound literal. Predict what -Wall -Wextra catches at compile time and what only AddressSanitizer catches at runtime; verify both. Deliverable: the two-column “caught by warnings / caught by ASan” table with observed compiler output.

Exercise 7.5 — Compile-time layout contracts. Invent a small “datasheet” for a fake peripheral: a register block with fixed offsets. Write the C struct overlay for it and pin the contract down with _Static_assert + offsetof for every register, a width assert for every field type, and _Alignas(32) on a DMA-style buffer whose alignment you then confirm via _Alignof, the linker map, and an lldb address check (MC 25–27). Break one assertion deliberately (reorder two members) and record the exact compile error — the point of the exercise is that the build fails, not the bench session. Cross-compile the same file to confirm the contract holds identically under arm-none-eabi-gcc.

Exercise 7.6 — A type-safe clamp with _Generic. Implement clamp_i16, clamp_i32, and clamp_f32 as static inline functions and dispatch them through one clamp(x, lo, hi) generic selection (MC 30). Contrast with the classic macro version: pass an argument with a side effect (counter++) through both and show — by predicted-then-observed output — that the _Generic/static inline version evaluates once while the macro double-evaluates. Then pass an unhandled type and record the diagnostic. In notes.md: one paragraph on where _Generic earns its keep in firmware (type-safe register/DSP helpers) and where it’s over-engineering (MC 30’s caveat).

Exercise 7.7 — restrict on the course dot product. Take Module 5’s naive C dot product and produce the four-way codegen comparison: with and without restrict-qualified pointers, at clang -O2 on A64 and arm-none-eabi-gcc -O2 on the M4 (MC 18):

A64, no restrict A64, restrict M4, no restrict M4, restrict
Inner-loop instruction count
Vectorized?
Loads per iteration

Deliverable: the table plus the API-contract paragraph — when restrict is an honest promise the caller can actually keep, what happens when it lies (UB, not a wrong answer you can test for), and why the CMSIS-DSP sources you read in Exercise 6.5 use it where they do.

Exercise 7.8 — Atomics without an OS. Probe what <stdatomic.h> really costs on each machine (MC 33–35). Write an atomic_uint event counter incremented from a “handler” and drained from a main loop, plus the same with a plain volatile uint32_t. Cross-compile at -O2 and annotate the disassembly: the atomic version’s LDREX/STREX retry loop vs. the volatile version’s plain (and racy) read-modify-write. Then fill the lock-free table by prediction and verify with atomic_is_lock_free on the host and the ATOMIC_*_LOCK_FREE macros plus emitted calls on the cross side:

Object size A64 lock-free? Cortex-M4 lock-free?
1 byte
2 bytes
4 bytes
8 bytes

Close with the fence distinction (MC 35): where atomic_signal_fence compiles to nothing but still matters, and what atomic_thread_fence emits on each ISA. This exercise is the language-level floor under Module 8’s ISR patterns and Course 2’s Module 7 RTOS work.