HomeAbout UsContact Us

Type Punning and Strict Aliasing in Embedded C

By embeddedSoft
Published in Embedded C/C++
June 01, 2026
3 min read
Type Punning and Strict Aliasing in Embedded C

Table Of Contents

01
Introduction
02
Understanding the Strict Aliasing Rule
03
Common Type Punning Scenarios in Embedded Systems
04
The __attribute__((may_alias)) and __attribute__((packed)) Approach
05
The memcpy Approach: Portable and Safe
06
Impact on Embedded Optimization
07
Practical Recommendations for Embedded Engineers
08
Summary
09
References

Introduction

One of the most subtle and frequently misunderstood topics in embedded C programming is the interaction between type punning and the strict aliasing rule. These low-level mechanisms govern how the compiler treats memory accesses through pointers of different types, and misunderstanding them can lead to subtle bugs, broken optimizations, or undefined behavior in production firmware. For embedded engineers working close to hardware—parsing peripheral registers, implementing communication protocols, or optimizing memory usage—a solid grasp of these concepts is not optional; it is essential.

In this article, we explore what the strict aliasing rule actually permits, when type punning is safe, and how to write portable code that compiles correctly under aggressive optimization levels commonly used in embedded toolchains.

Understanding the Strict Aliasing Rule

The C standard’s strict aliasing rule, defined in C99 section 6.5 paragraph 7, states that an object shall have its stored value accessed only by an lvalue expression of a compatible type (with exceptions such as character types, which may alias any object representation). In simple terms, if you have a float variable, you should not access it through an int* pointer—doing so invokes undefined behavior.

Why does this rule exist? Compilers use type-based alias analysis to determine whether two pointers might refer to the same memory location. If the compiler can prove that pointers of unrelated types do not alias, it can reorder reads and writes for better performance—critical for achieving tight loop performance on resource-constrained microcontrollers.

void process_values(int *a, float *b) {
*a = 10;
*b = 3.14f;
printf("%d\n", *a); // Compiler may cache *a in a register
}

In this function, the compiler knows that int* and float* are unrelated types, so it can assume that writing to *b does not affect *a. It may cache the value of *a before the write to *b, even if both pointers happen to point to the same address. This optimization is valid only because of the strict aliasing rule.

Common Type Punning Scenarios in Embedded Systems

Despite the rule, type punning is widespread in embedded firmware. Consider these typical use cases:

Reading Peripheral Register Values

// Reading a 32-bit peripheral register as individual bytes
uint32_t register_value = *((volatile uint32_t*)0x40021000);
uint8_t low_byte = register_value & 0xFF;
uint8_t high_byte = (register_value >> 8) & 0xFF;

This approach is safe because the CPU performs the type conversion through explicit bit manipulation rather than through pointer casting.

Protocol Parsing

// Parsing received data from a UART buffer
uint8_t rx_buffer[4];
// ... receive data into rx_buffer ...
uint32_t sensor_value = (uint32_t)rx_buffer[0]
| ((uint32_t)rx_buffer[1] << 8)
| ((uint32_t)rx_buffer[2] << 16)
| ((uint32_t)rx_buffer[3] << 24);

Union-Based Type Punning

The most common (and controversial) pattern in embedded code uses unions:

typedef union {
uint32_t as_uint32;
float as_float;
uint8_t as_bytes[4];
} converter_t;
float bytes_to_f32(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) {
converter_t c;
c.as_uint32 = ((uint32_t)b0 << 24)
| ((uint32_t)b1 << 16)
| ((uint32_t)b2 << 8)
| (uint32_t)b3;
return c.as_float;
}

Important note: Using unions for type punning is widely supported by most compilers (including GCC, Clang, and ARM compilers), but it is implementation-defined rather than strictly guaranteed by the C standard. In C++, it is undefined behavior. Since most embedded compilers support both languages differently, always check your compiler’s documentation.

The __attribute__((may_alias)) and __attribute__((packed)) Approach

GCC and Clang provide attributes that give you explicit control over aliasing behavior:

typedef uint32_t __attribute__((may_alias)) aliased_uint32_t;
void process_buffer(aliased_uint32_t *data, size_t count) {
for (size_t i = 0; i < count; i++) {
data[i] = data[i] ^ 0xDEADBEEF;
}
}

The may_alias attribute tells the compiler that this pointer may alias with pointers of any other type, preventing aggressive optimization from breaking your code.

The memcpy Approach: Portable and Safe

The most portable and standards-compliant way to perform type punning is using memcpy. Modern compilers typically optimize small memcpy calls into direct register moves when the size is known at compile time, resulting in zero or negligible runtime overhead:

#include <string.h>
float uint32_to_float(uint32_t value) {
float result;
memcpy(&result, &value, sizeof(float));
return result;
}
uint32_t float_to_uint32(float value) {
uint32_t result;
memcpy(&result, &value, sizeof(uint32_t));
return result;
}

This approach is guaranteed well-defined in both C and C++, and GCC, Clang, IAR, and ARM Compiler all optimize the memcpy into a direct register move when the size is small and known at compile time.

Impact on Embedded Optimization

On ARM Cortex-M processors, the difference between correct and incorrect aliasing handling can be significant. When the compiler can assume no aliasing, it keeps values in registers across loop iterations, reducing memory accesses. With incorrect aliasing assumptions, the compiler generates correct but suboptimal code—extra load and store instructions that waste cycles and increase power consumption.

Consider a typical DSP loop:

void process_samples(int16_t *output, const int16_t *input, size_t n) {
for (size_t i = 0; i < n; i++) {
int32_t acc = (int32_t)input[i] * 0x7FFF;
output[i] = (int16_t)(acc >> 15);
}
}

If output and input could point to overlapping memory, the compiler must reload input[i] after every write to output[i]. Using the C99 restrict qualifier tells the compiler (by programmer guarantee) that the pointers do not overlap:

void process_samples(int16_t * restrict output,
const int16_t * restrict input,
size_t n) {
// Compiler can now safely keep input[i] in a register
}

This single qualifier can yield 20–40% performance improvement on tight DSP loops by enabling more efficient register allocation.

Practical Recommendations for Embedded Engineers

  1. Always use memcpy for type punning—it is portable, well-defined, and optimized away by modern compilers
  2. Use const and restrict qualifiers on pointer parameters to give the compiler maximum optimization freedom
  3. Use union-based punning only when you control the compiler and have verified the behavior
  4. Access hardware registers through volatile pointers of the correct type rather than through type-punned accesses
  5. Avoid -fno-strict-aliasing as a global workaround—it disables beneficial optimizations throughout your entire codebase instead of fixing the root cause
  6. Write unit tests that run with optimization enabled—many aliasing bugs only manifest under -O2 or -O3

Summary

The strict aliasing rule exists to enable better compiler optimization, but it can catch embedded engineers off guard when working with raw memory, peripheral registers, and protocol buffers. By understanding which forms of type punning are well-defined and which invoke undefined behavior, you can write firmware that is both correct and performant. Prefer memcpy for portability, use restrict to unlock optimization opportunities, and always verify aliasing-sensitive code under your target optimization level. These practices separate reliable firmware from code that only works by accident.

References


Tags

type-punningstrict-aliasingc-programmingembedded-coptimization

Share


Previous Article
SPI Communication Protocol Explained for Embedded Systems
embeddedSoft

embeddedSoft

Embedded Systems Articles by Jithin Tom & Hermes (AI Agent)

Related Posts

Memory Pool Allocation for Deterministic Embedded Systems
Memory Pool Allocation for Deterministic Embedded Systems
May 29, 2026
5 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media