Introduction: The Role of Addresses in C

When learning C, it is natural to wonder why the language forces us to manage dedicated pointer variables (int *p) instead of just relying on the address-of operator (&x) on the fly. After all, &x already yields the memory address where the data lives.

However, from both a computer architecture and compiler design perspective, treating memory addresses strictly as temporary rvalues (expressions without storage) would fundamentally break C's capabilities. Let's explore the core limitations that make dedicated pointer variables indispensable.

1. The Limitation of Pure Rvalue Addresses

The expression &var produces an rvalue—a temporary value that has no storage of its own. It represents a constant offset determined either at link time (for global variables) or relative to the stack frame pointer (for local variables) at runtime.

If a language only permitted addresses as rvalues and lacked variables capable of holding those addresses, an address could only ever refer to a variable that has already been declared statically or on the stack. You could not dynamically retarget, pass around, or retain addresses beyond the direct scope of the named identifier.

2. Dynamic Memory Allocation Breaks Down

Consider dynamic memory allocation functions like malloc() or system calls like mmap():

void *buffer = malloc(1024);

Heap allocations do not come with variable names. malloc allocates raw memory from the heap and returns its base address. Without a pointer variable to store this address, you would have no mechanism to refer to this newly created memory. Because there is no existing identifier to use with &, dynamic memory allocation simply cannot exist without first-class pointer variables.

3. Linked and Self-Referential Data Structures Are Impossible

Pointer variables allow data structures to evolve dynamically at runtime. Classic dynamic data structures—such as linked lists, binary trees, graphs, and hash tables—depend directly on the ability to store and reassign addresses inside structures.

struct Node {
    int data;
    struct Node *next; // Cannot exist without pointer variables
};

If you could only use &, every reference would have to be known and bound to an existing variable name at compile time. You could create fixed arrays, but you could never build a runtime-sized singly linked list or splice a node into the middle of a chain, because reassignment (e.g., node->next = new_node) requires storing an address into mutable memory.

4. Loss of Runtime Indirection and Function Pointers

Indirection is the superpower of low-level programming: the ability to decouple an operation from the specific data or function being acted upon. Pointers provide dynamic indirection, which would vanish without pointer variables:

  • Pointer Reassignment: A single pointer can iterate through an array or traverse a buffer simply by incrementing its stored address (p++). An expression like &x is immutable—you cannot write &x = &x + 1.
  • Polymorphism and Callbacks: Function pointers (e.g., int (*cmp)(const void*, const void*)) allow functions like qsort to execute arbitrary logic. Without a variable capable of holding a code address, callbacks and virtual tables (vtables) cannot be built.
  • Memory-Mapped I/O: Low-level hardware programming requires assigning arbitrary addresses to pointers (e.g., volatile uint32_t *reg = (uint32_t *)0x40021000;). With only the & operator, you cannot map code to specific hardware registers.

5. References vs. Pointers: The Compiler Perspective

Modern languages like C++ introduce references (int &ref = x;). Even so, under the hood, compilers almost universally implement references as constant pointers that are automatically dereferenced.

Languages that avoid visible pointers (like Java, Python, or Go) did not eliminate pointer variables—they simply turned almost every non-primitive variable into an implicit, reference-managed pointer. At the assembly level, a CPU executes loads and stores via registers holding memory addresses (e.g., mov [rax], rbx). To leverage general-purpose register architectures, a language needs a type that mirrors an address register: an assignable pointer.

Conclusion

The address-of operator & only answers the question: "Where is this specific, named variable?"

Pointer variables, on the other hand, answer a far more powerful set of needs: dynamic allocation, runtime indirection, complex data graph representation, and flexible memory iteration. Without assignable pointer variables, C would be reduced to a static, rigid language incapable of dynamic resource management or systems-level programming.