Packed MCU Structures: Audit Unaligned Access Before Mapping a Wire Buffer
A packed member can be valid to access through its packed structure while being unsafe to pass as an ordinary scalar pointer. The receiving function sees a uint32_t *, not the original packed-member context. Audit every address-taking operation, then prove the alignment of the actual member instance before allowing that pointer to escape.
This guide uses C11 and GCC 14.2.0’s packed extension. The executed fixture ran on x86-64 Linux; it establishes compiler diagnostics and host alignment-sanitizer behavior, not MCU fault behavior or instruction timing. Peripheral registers, DMA and C++ lifetime rules are outside scope. A wire-buffer overlay also needs independent bounds, representation, byte-order and object-access checks.
Separate structure layout from the callee’s contract
The GCC 14.2.0 type-attribute manual describes packed layout and notes that packing an outer structure does not recursively pack a nested structure’s internal layout. Therefore neither a compact outer size nor one packed declaration proves every nested field’s offset.
For each shared structure, record its size, alignment, member offsets, member sizes and any nested types. Then list functions that accept addresses into it. An ordinary scalar-pointer API usually carries the scalar type’s alignment requirement. A cast that suppresses a warning changes neither the member address nor the instruction assumptions inside that function.

Calculate addresses, not just type alignments
For element i in an array, the member address is base plus i times the structure size plus the member offset. Its remainder modulo the member’s ordinary alignment determines whether that particular address meets the alignment requirement. Use the compiler’s actual layout values for the selected build; do not substitute a remembered MCU ABI.
The original host fixture declared a packed record containing an unsigned-character tag followed by a uint32_t value. GCC reported size five, structure alignment one, value offset one and ordinary uint32_t alignment four. With the array base explicitly aligned to sixteen, four consecutive value addresses had remainders one, two, three and zero modulo four.
Those are measured host layout results. They illustrate why aligning the array’s beginning is insufficient: the five-byte stride moves each later field to a different residue. On another ABI, recompute the entire ledger. Likewise, adding an alignment attribute to the structure may change its total size without moving an already packed member to the offset that an external API requires.
Keep direct access and pointer escape as separate tests
The fixture used these original functions:
uint32_t direct(const struct record *p) {
return p->value;
}
uint32_t ordinary(const uint32_t *p) {
return *p;
}
The caller’s ordinary(&items[0].value) was an intentional negative test. GCC emitted its packed-member-address warning. The versioned warning manual documents that diagnostic. Treat it as a review trigger, rather than a request to insert a cast or globally disable warnings.
With -fsanitize=alignment -fno-sanitize-recover=alignment, that escaped-pointer load produced a misaligned-load diagnostic and exit status one. Direct packed-field reads and byte copies into aligned local uint32_t objects passed their value checks. The instrumentation manual defines the alignment checks. This was a deliberately failing host test, not an observed MCU hard fault.
The noninstrumented optimized object was also disassembled. Target qualification must repeat that inspection with the real cross compiler and flags. A host instruction that tolerates an unaligned address does not establish that an MCU instruction, memory region or configuration will do so.
Choose an interface that preserves the needed information
- Pass a value: read the member through the declared packed type, then pass the resulting scalar. The callee no longer receives a potentially misaligned address.
- Copy into an ordinary object: copy the member’s bytes into a separately declared local scalar before calling an API that requires a scalar pointer. Keep representation and byte-order conversion explicit.
- Use a byte-oriented interface: pass a byte pointer and length to a parser whose contract permits unaligned input and validates the complete field extent.
- Redesign storage: keep wire bytes separate from naturally aligned application records when pointer-bearing interfaces are common.
Do not automatically copy a whole received frame into a native structure. Packing controls layout under a compiler extension; it does not define a protocol’s endian convention or validate arbitrary bytes as every possible field type. The WG14 N1570 C11 draft, sections 6.3.2.3 and 7.24.2.1, provides the relevant pointer-alignment and nonoverlapping byte-copy rules.
A hypothetical parser regression
Suppose a hypothetical sensor decoder originally reads a packed sample value directly. A refactor sends its address to a reusable conversion helper. Ordinary unit tests use one conveniently placed object and pass. A ring of five-byte records later exposes a failure only at some positions.
Reproduce the layout with an array, not another isolated object. Log the element index and calculated alignment remainder without dereferencing the questionable pointer. Compare the direct-access path, escaped-pointer negative path and aligned-copy repair. The desired result is an explained interface violation and a repair that works at every supported position, not merely another address where the symptom disappears.
Release with an access audit
- Freeze compiler, CPU options, ABI, optimization and relevant packing pragmas.
- Generate the size, alignment, stride and offset ledger from the actual headers.
- Search address-taking, casts, callbacks and library calls for escaped member pointers.
- Run boundary-position tests covering all alignment residues reachable by the stride.
- Inspect final target instructions and consult the exact processor and memory-region requirements.
- Keep negative diagnostics separate from successful repaired-path tests.
Acceptance requires every ordinary pointer to meet its callee’s contract, or the interface to stop requiring that pointer. A smaller structure and a successful desktop run are useful observations, but neither closes the target access audit.