Home CodingPointer Arithmetic, Type Punning, and the Alignment Trap

Pointer Arithmetic, Type Punning, and the Alignment Trap

by dnaadmin

In a high-level language, a pointer is just a reference. In C and C++, a pointer is a memory address, and how you manipulate that address can either make your driver highly efficient or cause a hardware “Bus Error” that is nearly impossible to debug. For a System Architect, understanding Memory Alignment and Strict Aliasing is non-negotiable.


1. The Mechanics of Pointer Arithmetic

The most common mistake junior engineers make is assuming ptr + 1 always adds one byte to the address.

  • The Rule: Adding n to a pointer of type T* increments the address by $n \times \text{sizeof}(T)$.

  • Example: On a 32-bit system:

    • uint8_t *p8; p8 + 1; → Adds 1 byte.

    • uint32_t *p32; p32 + 1; → Adds 4 bytes.

    • void *pv; pv + 1;Undefined Behavior (though many compilers treat it as 1 byte, it is non-standard).


2. The Alignment Trap: Why 0x1001 is Dangerous

In the semiconductor world, hardware usually expects data to be “naturally aligned.” A 32-bit integer should start at an address divisible by 4.

The Scenario: You are parsing a packet from a network buffer.

C

uint8_t buffer[10] = {0x00, 0xAA, 0xBB, 0xCC, 0xDD, ...};
uint32_t *val = (uint32_t *)&buffer[1]; // Unaligned access!
  • x86 Architecture: Usually handles this in hardware with a slight performance penalty.

  • ARM/RISC-V: Depending on the configuration, this might trigger an Alignment Fault (UsageFault), crashing the system instantly.

The Architect’s Solution: Use memcpy or __attribute__((packed)) to handle unaligned data, or better yet, design your hardware structures to be naturally aligned from the start.


3. Type Punning and Strict Aliasing

“Type Punning” is the act of accessing the same memory location as two different types. While common in embedded code, it can trigger Strict Aliasing optimizations that break your logic.

The Danger:

C

void swap_halves(uint32_t *ptr) {
    uint16_t *half = (uint16_t *)ptr; 
    // The compiler may assume *ptr and *half never point to the same memory
    // and reorder reads/writes in a way that breaks your logic.
}

Modern compilers (GCC/Clang with -O2 or -O3) assume that pointers of different types do not alias. To safely type-pun, architects use a union or memcpy.


4. The “Architect’s Question”: void* vs uintptr_t

Q: When should you use uintptr_t instead of void*?

A: Use void* when you want to point to “opaque” data that the CPU will eventually dereference. Use uintptr_t (from <stdint.h>) when you need to perform arithmetic on addresses (like calculating a page offset or masking bits). uintptr_t is an unsigned integer guaranteed to be large enough to hold a pointer, making it safe for bitwise operations like addr & ~0xFFF.


5. Summary Table: Pointer Operations

Operation Best Type to Use Why?
Passing data to a function void* Generic, hides internal structure.
Stepping through a byte array uint8_t* or char* Increments by exactly 1 byte.
Bitwise address masking uintptr_t Integers allow &, `
Direct Hardware Register Access volatile uint32_t* Ensures every read/write hits the silicon.

Architect’s Interview Tip

If asked to parse a binary header, don’t just cast a pointer to a struct. Mention that you are aware of Endianness and Padding. A struct on a 64-bit system might have different internal padding than on a 32-bit system. Using __attribute__((packed)) or explicit padding members shows you design for portability across different silicon.


In the next article, we look at the “hidden” side of variables: The const Qualifier, constexpr, and the Symbol Table.

Ready for Article 4?

You may also like

Leave a Comment