Skip to content

Memory, Arrays and Loads

A pointer contains an address; a load reads bytes at that address. Walk a C array while keeping the length, element width and accumulation width separate.

Before you start

Complete branches and loops. You can use a zero-length guard and trace a loop. Work in a fresh lesson directory on the Pi.

Outcome: sum valid array elements without reading an empty array, and distinguish zero-extension from sign-extension.

Address, element and byte count

For this native 64-bit target, a pointer occupies eight bytes. An int32_t element occupies four. Advancing a pointer to the next element therefore adds 4, not 1 or 8.

Form Effect
ldr w2, [x0] Read four bytes, zero-extend into x2
ldrsw x2, [x0] Read four bytes, sign-extend into x2
ldr x2, [x0] Read eight bytes
str w2, [x0] Write four bytes
[x0], #4 Access the current address, then advance by four

Assembly receives no automatic array bounds. A nonzero length requires a readable array of that many elements. Null is accepted only when the length is zero, because no access occurs. The code does not validate arbitrary pointers.

Build the memory lab

This routine sums signed 32-bit elements into a 64-bit accumulator. The caller limits the length to 1024, which also keeps the mathematical sum within int64_t.

// file: memory.S
.text
.global sum_i32
.type sum_i32, %function
sum_i32:
    mov x2, #0
    cbz x1, .Ldone
.Lloop:
    ldrsw x3, [x0], #4
    add x2, x2, x3
    subs x1, x1, #1
    b.ne .Lloop
.Ldone:
    mov x0, x2
    ret
.size sum_i32, .-sum_i32

.global zero_word
.type zero_word, %function
zero_word:
    ldr w0, [x0]
    ret
.size zero_word, .-zero_word
.section .note.GNU-stack,"",%progbits
/* file: memory.c */
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
extern int64_t sum_i32(const int32_t *, uint64_t);
extern uint64_t zero_word(const uint32_t *);
int main(void)
{
    const int32_t values[] = {-1, 2, -3, 4};
    const uint32_t word = UINT32_MAX;
    if (sum_i32(0, 0) != 0 || sum_i32(values, 1) != -1 ||
        sum_i32(values, 4) != 2 ||
        zero_word(&word) != UINT64_C(0xffffffff)) return 1;
    printf("sum: %" PRId64 "\n", sum_i32(values, 4));
    puts("PASS memory: empty, signed loads, zero extension");
    return 0;
}
gcc -std=c11 -Wall -Wextra -Wpedantic -Werror -g -O0 memory.c memory.S -o memory
./memory
sum: 2
PASS memory: empty, signed loads, zero extension

Read the loop as a contract

At loop entry, x0 points to the next unread element and x1 is the remaining count. x2 holds the sum of elements already read. Each iteration consumes exactly one element. The zero guard is what makes an empty range safe, not a special property of address zero.

Changing ldrsw to ldr w3 makes the negative test fail: −1 becomes 4294967295 in the accumulator. Changing the stride to eight skips elements and can overrun the array. Do not intentionally run an out-of-bounds experiment; inspect the address calculation on paper or in GDB.

For stores, prove the output range is writable and large enough before entering the loop. Readability, writability and alignment are distinct obligations. These examples use naturally aligned C arrays, not device registers or packed structures.

Check your understanding

What happens if you double the byte stride without changing the input length? Why is null plus zero length acceptable here?

Compare your reasoning

The routine would skip elements and eventually read outside the array. Null with zero length is never dereferenced because the branch goes directly to the return path.

Continue the course

Previous: Flags, Branches and Loops · Course roadmap · Next: Addresses and Relocations

Donate