Dereferencing (*): Piercing Through Memory

Hello embedded engineers and developers of the 123microcontroller community! Welcome back to another technical deep dive with your favorite sleep-deprived engineer.

In our previous article, we explored the & (Address-of Operator), which is used to “request the coordinates” of a variable. But imagine this: if you hold the exact memory coordinates of a temperature sensor or a motor control register in your hands, but you cannot actually travel there to read or modify the data, those coordinates are practically useless, right?

In the C programming universe, the master key that allows us to “travel to the coordinates” and manipulate the data at the destination is Dereferencing (or Indirection), using the Asterisk operator (*). Today, we will decode how the * operator works, how it relies on Data Types, and why it is the absolute beating heart of System Programming. Let’s dive in!

The Mechanics of Indirection (Dereferencing)

In the context of pointer fundamentals, expert software architects explain the process of Dereferencing (also known as Indirection) as follows:

1. Following the Pointer

The process of following a pointer’s trail to the actual memory location it points to is called Indirection or Dereferencing. The tool used for this job is the Unary * operator, sometimes referred to as the “Value at” operator. Think of it like looking at a map (the Address), driving to the house, and opening the front door to see what is inside.

2. The Dual Roles (R-value and L-value)

When we place the * operator in front of a pointer, it can serve two entirely different purposes depending on which side of the equation it sits:

  • Reading Data (R-value): If it sits on the right side of an assignment (e.g., y = *ptr;), it travels to the memory location and “fetches” the data stored there to be used.
  • Writing Data (L-value): If it sits on the left side (e.g., *ptr = 200;), it acts as a “Location specifier,” targeting that specific memory address so you can overwrite the destination with a new value (200).

3. Type Interpretation (The Hardware Secret)

This is the most critical concept for bare-metal engineers! When we issue a dereference command (*ptr), how does the compiler know exactly how many bytes to read or write? The answer lies entirely in the “Pointer’s Data Type”. If the pointer is an int32_t * (32-bit), calling *ptr will fetch exactly 4 bytes and interpret them as an integer. If it is a char *, it will fetch only 1 byte. The pointer’s type completely dictates the behavior of the dereference.

4. Multiple Indirection (Pointer to Pointer)

If we have a pointer that points to another pointer (e.g., int **ppi;), we can stack the * operators. Executing **ppi means: read the first pointer to find the next address, travel to that second address, and finally access the raw data residing at the final destination.

Diagram showing how Dereferencing works as L-value and R-value

Code Example: Mastering the * Operator

Let’s look at a clean, bare-metal style C code example demonstrating reading (R-value), writing (L-value), type interpretation, and multiple indirection.

#include <stdio.h>
#include <stdint.h>

int main(void) {
    /* Simulating a 4-byte (32-bit) hardware register */
    uint32_t hardware_register = 0x11223344;

    /* 1. Create a pointer aiming at the hardware_register */
    uint32_t *ptr_reg = &hardware_register;

    /*
     * 2. Dereferencing as an R-value (Reading)
     * Fetch the value from the targeted address (0x11223344) and print it.
     */
    printf("Current Register Value: 0x%X\n", *ptr_reg);

    /*
     * 3. Dereferencing as an L-value (Writing)
     * Overwrite the destination address with a new value: 0xFFFFFFFF
     */
    *ptr_reg = 0xFFFFFFFF;
    printf("New Register Value: 0x%X\n", hardware_register);

    /* =========================================================
     * 4. The Importance of Data Type during Dereferencing
     * ========================================================= */
    /* Use a byte pointer (reads 1 byte at a time) pointing to the same address */
    uint8_t *byte_ptr = (uint8_t *)&hardware_register;

    /* When dereferencing byte_ptr, the compiler strictly reads only 1 byte (0xFF) */
    printf("Reading just 1 byte: 0x%X\n", *byte_ptr);

    /* =========================================================
     * 5. Multiple Indirection (Pointer to Pointer)
     * ========================================================= */
    uint32_t **ptr_to_ptr = &ptr_reg;
    
    /* Dereference twice to modify the original hardware_register variable */
    **ptr_to_ptr = 0x00000000;
    printf("Register Value after **: 0x%X\n", hardware_register);

    return 0;
}

Best Practices & Fatal Traps

Dereferencing a pointer is like firing a laser beam. If you aim it at the wrong target, you will destroy the system! Expert C textbooks and Secure Coding guidelines emphasize these critical warnings:

  • NEVER Dereference a NULL Pointer: By definition, a NULL pointer points to absolutely nothing (Address 0). Attempting to place a * in front of a NULL pointer will immediately result in Undefined Behavior or a fatal Segmentation Fault, crashing your program. Golden Rule: Always check if (ptr != NULL) before dereferencing.
  • The Danger of Uninitialized Pointers: When you declare a pointer (e.g., int *pi;), it holds random memory garbage. If you immediately execute *pi = 10;, you are forcing the value 10 into a completely random memory location. This could overwrite another variable’s data or cause the OS to aggressively terminate your application.
  • Beware of Casting and Dereferencing (Buffer Overflow): If you take a small memory space (like a 1-byte char), cast its address to an int32_t *, and then dereference it to write data (e.g., *int_ptr = 1000;), those 4 bytes of data will violently overflow the 1-byte boundary, corrupting adjacent memory! This is a severe security vulnerability.

Conclusion

Accessing destination data through Dereferencing (*) is the core mechanism that allows C to truly manipulate memory and control hardware. It acts as both a tool for “Reading” (R-value) and “Writing” (L-value), with the pointer’s Data Type silently governing its behavior in the background. Mastering this mechanism guarantees you can write embedded code that is both highly optimized and relentlessly safe.

Have you ever spent hours chasing a bug caused by accidentally dereferencing a NULL pointer, or gotten dizzy looking at ** double pointers? Don’t suffer alone! Drop your code and join the discussion on our community board at www.123microcontroller.com! See you in the next article. Happy Coding!