Arrays, strings and (a0)+
Walking memory with an address register that steps itself, indexing it with a scaled register instead, and the two loops that every string on this machine is made of.
The overview of this topic is in Assembly basics. The same topic in MIPS, RISC-V, Z80.
An array in memory has no length, no bounds and no element names. What it has is a first address and a size per element, and every loop over it is built out of those two numbers. The M68K gives you two ways to write that loop.
Walking with (a0)+
(a0)+ reads what a0 points at and then adds the size of the instruction to a0, so a loop that
uses it needs no add of its own and no index at all.
d0 comes out at 00000096, which is 150. Step through the loop and watch a0 climb by 4 at every
add.l, from 00002000 to 00002014, twenty bytes on and one past the last element.
The 4 is in the .l, nowhere else. Change the array to dc.w and the instruction to add.w and
the same loop steps by 2, because the size of the read decides the step. That is the M68K's version
of C hiding the size of *p++, and it is the one place assembly does the arithmetic for you.
count equ 5 gives the length a name. The loop counts with count-1 because dbra runs one more
time than its counter, and the assembler does the subtraction, so adding a sixth number means
changing the equ and nothing else.
Indexing with (a0, d1)
The other way keeps the base address still and works out the offset every time, which is what
numbers[i] compiles to.
d0 is 150 again, out of four more instructions per pass. The scaling is the extra work: numbers[i]
in C means the base plus i times 4, the M68K adds a register and nothing else, and lsl.l #2 is
how you multiply by 4 without a mulu.
So which one. Use (a0)+ when you touch every element in order, which is most loops. Use (a0, d1)
when you need the index itself, to report where you found something; when the loop jumps around the
array instead of walking it, the way a binary search does; or when you read two elements per pass and
the second one is 4(a0, d1).
Strings are bytes with a zero at the end
dc.b 'Hello', 0 writes six bytes: the five character codes and the terminator you wrote yourself.
Nothing in memory says how long the string is, so a loop finds out by reading until it reads a zero.
The instruction that does the reading also sets Z, so no cmp is needed:
d0 comes out at 5, the five characters without the terminator, and a0 at 00002006, one past it.
Then the second loop copies the string to copy at $2006, and the memory panel shows the same six
bytes twice: 48 65 6C 6C 6F 00 and then 48 65 6C 6C 6F 00.
move.b (a0)+, (a1)+ is the whole of C's strcpy in one instruction plus a branch. It works because
move sets Z from the byte it moved, so the terminator both ends the loop and gets copied, which
is what you want: a copy without a terminator is not a string.
Comparing two strings has an instruction of its own. cmpm.b (a0)+, (a1)+ compares the bytes at
a0 and a1 and steps both, which is strcmp without loading either byte into a register.
Two dimensions
A 2D array is a 1D array read in rows. grid[row][col] is the base plus (row * COLS + col) times
the size of an element, and the M68K makes you write both multiplications.
d3 comes out at 00000017, which is 23, the last element of the last row. add.l d2, d2 is the
multiplication by 2, since adding a number to itself is cheaper than a mulu and every size on this
machine is a power of two.
The three dc.w lines are one array: the rows are a convenience for whoever reads the source, and
the twelve words sit end to end at $2000, which is what COLS in the index arithmetic assumes.
Try changing move.l #2, d0 to move.l #0, d0 and move.l #3, d1 to move.l #1, d1. d3 comes
out at 1, the second element of the first row.
The address error, again
Words and longs in an array have to land on even addresses. The elements themselves are fine, since
an array of words starting even stays even, and it is the byte data next to them that moves
everything: a dc.b of an odd number of bytes above an array of words puts the whole array on odd
addresses, and the first move.w into it ends the run. Put byte data last, or give it an even
length.
Your turn
text at $2000 is a string with a zero at the end. Leave its length, not counting the terminator,
in d0. For 'Assembly' that is 8.
Show solution
The second one copies source to dest, terminator included. source is at $2000 and holds nine
bytes, so dest begins at $2009.