Arrays Versus Pointers at the Hardware Level

In C, arrays and pointers often look interchangeable, and many programmers treat them as if they were the same thing. At the hardware level, however, they compile down to noticeably different instruction sequences with different performance characteristics. This article compares the two approaches using RISC-V assembly to show exactly why pointer-based code is often faster.

Arrays vs PointersPointer ArithmeticRISC-V Address Calculation

~3 min read · Updated Sep 6, 2026

Why This Comparison Matters

In high-level C code, walking through an array using index notation and walking through it using a pointer both look natural and often produce the same logical result. But a compiler translates each style into a distinct pattern of RISC-V instructions, and the resulting hardware work is not identical. Understanding this difference is a common source of insight into why certain coding styles run faster in practice.

The Array-Indexing Approach

Consider a simple loop that clears every element of an array using index notation:

void clear1(long array[], long size) {
    for (long i = 0; i < size; i += 1) {
        array[i] = 0;
    }
}

To translate array[i] into a memory access, the processor must, on every single loop iteration, recompute the memory address by multiplying the index i by the size of each element, then adding that result to the array's base address:

Loop:
bge i, size, Exit
slli t0, i, 3
add t1, array, t0
sd x0, 0(t1)
addi i, i, 1
jal x0, Loop
Exit:

Notice the extra slli (shift left, used here to multiply by 8 bytes) and add instructions needed on every iteration purely to compute the address from the index.

The Pointer-Based Approach

Now consider the same logic written using pointer arithmetic instead of indexing:

void clear2(long *array, long size) {
    long *p;
    for (p = &array[0]; p < &array[size]; p = p + 1) {
        *p = 0;
    }
}

Here, the compiler can generate a loop where the pointer itself is directly incremented by a fixed amount each iteration, without ever recalculating an address from an index:

Loop:
bge p, end_p, Exit
sd x0, 0(p)
addi p, p, 8
jal x0, Loop
Exit:

This version eliminates the multiplication step entirely, since the pointer already holds a real memory address and only needs a simple fixed addition to move to the next element.

Why the Difference Exists

An array index is just a number that must be converted into an address every time it is used, requiring a multiplication by the element size on each access. A pointer, in contrast, already is an address, so advancing it to the next element only requires adding the element's fixed size once, rather than recomputing a product from scratch.

Practical Takeaway

Modern optimizing compilers are often capable of automatically transforming index-based loops into pointer-based ones internally, a process called Strength Reduction, which can eliminate this performance gap without the programmer needing to rewrite the code manually. However, understanding the underlying hardware difference explains why, historically, and in cases where a compiler cannot safely perform this optimization, pointer-based iteration has been recommended for performance-sensitive code.

Written & researched by Dr. Shahin Siami

Related Articles

Control Hazards: Handling Branches in a Pipelined Processor

Branches create a unique problem for pipelining: the processor must fetch the next instruction before it even knows whether a branch will be taken. This article explains what control hazards are, how branch prediction and delayed resolution attempt to minimize their cost, and what happens when a prediction turns out to be wrong.

Continue

Data Hazards in Pipelines: Forwarding Versus Stalling

Overlapping instruction execution creates a serious problem when one instruction needs a result that a previous instruction has not finished computing yet. This article explains what data hazards are, how forwarding solves most of them without losing any performance, and why some situations still require the pipeline to stall.

Continue

Turning a Single-Cycle Datapath into a Pipelined One

Overlapping instruction execution requires more than just running the same single-cycle hardware faster; it requires physically separating each pipeline stage with storage elements and duplicating control logic across stages. This article explains how pipeline registers preserve instruction state between stages and how control signals travel alongside data through the pipeline.

Continue

An Overview of Pipelining: Overlapping Instruction Execution

A single-cycle processor wastes enormous amounts of hardware idle time since every instruction must fit within the length of the slowest possible instruction. This article introduces pipelining as a solution, explains the classic assembly-line analogy, breaks down the standard five-stage pipeline, and covers why pipelining increases instruction throughput without making any individual instruction faster.

Continue

Designing Control Logic for a Single-Cycle Processor

A datapath alone does nothing without control signals telling it what to do for each instruction. This article explains how control logic reads an instruction's opcode and function fields to generate the exact signals needed to route data correctly, and walks through how a complete single-cycle implementation executes different instruction types.

Continue

Building a Datapath: Connecting Registers, Memory, and the ALU

A datapath is the physical circuitry that moves data through a processor as it executes an instruction. This article breaks down the essential hardware building blocks needed to fetch, decode, and execute instructions, and shows how they are wired together to form a functioning, if simplified, processor datapath.

Continue