An unaligned access error occurs when a program attempts to access data that is not aligned to the natural alignment of the processor. This typically happens when trying to access data types like integers that have stricter alignment requirements than a single byte. On architectures like ARM, which generally require aligned accesses for performance and correctness, an unaligned access can generate an exception or fault.
What Causes Unaligned Accesses?
There are several common causes of unaligned accesses:
- Accessing fields within a packed struct: If a struct contains fields with different data sizes packed together, individual fields may end up at unaligned addresses.
- Type punning through unions: Unions allow overlaying different data types on the same memory. This makes it easy to reinterpret data at an unaligned address.
- Pointer arithmetic: Incrementing a pointer by anything other than the element size can create unaligned pointers.
- Array indexing: Indexing array elements as bytes can create unaligned pointer accesses.
- Poor data layout: Padding and alignment issues in data structures can embed unaligned fields.
- Unoptimized compiler output: The compiler may generate unaligned accesses, especially at higher optimization levels.
Unaligned accesses may also occur when interfacing with hardware devices that present data at unaligned addresses. Sharing data structures between compilation units (e.g. ASM and C) can also lead to misaligned expectations.
Problems Caused by Unaligned Accesses
There are several issues that can occur due to unaligned accesses:
- Performance degradation: Unaligned loads and stores require extra instructions to handle the address, which impacts performance.
- Exceptions/Faults: Many architectures like ARM will generate exceptions or faults on unaligned accesses, which can crash programs.
- Incorrect behavior: Unaligned accesses can silently corrupt data by crossing element boundaries.
- Endianness issues: Unaligned accesses may have different behavior between big and little endian systems.
For performance-critical and embedded software, unaligned accesses should be avoided whenever possible. The exceptions and possibility of silent data corruption make them especially dangerous in safety-critical code.
Detecting Unaligned Accesses
There are several ways to detect unaligned accesses:
- Compiler warnings: Compile with flags like
-Wcast-align
enabled to catch alignment issues. - Dynamic checking: Instrument loads and stores to check alignment at runtime.
- Hardware exceptions: Enable alignment fault exceptions to catch issues as they occur.
- Memory access profiling: Use a tool to track all memory access and spot unaligned patterns.
- Fuzz testing: Fuzzing with alignment-focused corpora can trigger unaligned crashes.
Compiler warnings provide the first line of defense during development. But due to complex interactions, some unaligned accesses may only manifest at runtime. A combination of runtime instrumentation, hardware exceptions, and targeted testing provides better coverage of potential issues.
Fixing Unaligned Accesses
There are also various methods to fix unaligned accesses:
- Re-arranging data structures for proper alignment.
- Introducing padding fields to align fields.
- Allocating data on aligned boundaries.
- Copying data to/from aligned buffers.
- Explicitly packing/unpacking smaller element accesses.
- Issuing aligned instructions (slow!).
- Byte-addressable non-temporal stores to avoid merging.
Fixing unaligned accesses should focus on maintaining aligned data layout as much as possible. This usually involves changes to data structures and how they are allocated/initialized. Accessing packed data can be fixed by unpacking elements into temporary aligned buffers. As a last resort, unaligned instructions or non-temporal stores may be used, but these incur significant performance costs.
Dealing with Unaligned Accesses
Sometimes it is difficult to fully eliminate unaligned accesses, for example when interfacing with external hardware. There are ways to safely handle them in these situations:
- Isolate unaligned access code from performance-critical paths.
- Utilize alignment-agnostic access primitives like
memcpy
. - Temporary buffering via aligned stack variables.
- Disable related alignment exceptions/faults.
- Volatile accesses to force as-if serial execution order.
Isolating unaligned access code in cold paths, saving and restoring alignment state, and using locked transfers or DMA can help contain the impact. Multi-byte accesses should be broken down via memcpy
or byte access loops to avoid crossing element boundaries. Disabling faults should be limited to only where required and accompanied by exhaustive testing.
Unaligned Access Support in Hardware
Some architectures provide special support for unaligned accesses:
- Hardware unpacking/re-packing of misaligned data (e.g. ARMv6)
- Unaligned load/store instructions (x86, SPARC)
- Configurable unaligned access behavior (MIPS, RISC-V)
- Unaligned access traps/exceptions (ARM, RISC-V, PowerPC)
This enables efficient unaligned access in specific controlled circumstances. But aligned access is still preferable when possible. Unaligned handling comes with trade-offs like added hardware complexity and exceptions still being needed in some cases.
Guidelines for Avoiding Unaligned Accesses
Some general guidelines for avoiding unaligned accesses:
- Understand architecture’s alignment requirements.
- Stick to aligned types like uint32_t vs byte access.
- Use aligned allocation (e.g. memalign, posix_memalign).
- Structure data for natural alignment.
- Avoid packing disparate data sizes.
- Use compiler’s native vector types.
- Prefer array indexing over pointer arithmetic.
- Avoid type-punning through unions.
Testing methodologies like fuzzing, dynamic instrumentation, and hardware traps should also be used to validate there are no unaligned accesses in production. Following these guidelines from the start will prevent many issues down the road.
Conclusion
Unaligned accesses are a common source of bugs and performance issues, especially on architectures like ARM. They are caused by mismatched data layout and unsafe pointer manipulation. Runtime crashes, data corruption, and reduced performance are all risks. Aligning data, validating with testing tools, and fixing with access primitives like memcpy form a robust strategy against unaligned access bugs.