Data in memory

Arrays and strings, the directives that write them into memory or reserve room for them, and how a program walks one with a pointer. The memory panel is open next to both programs.

A CPU has between eight and thirty two registers, and a program usually has more to keep than that. Everything else lives in memory, which we saw is one large array of bytes, and the program reaches it with the indirect and indexed modes: an address in a register, and the CPU goes and reads what is there.

Telling the assembler to put something there

Data does not appear in memory by itself, a directive puts it there while the program is being assembled. The M68K's are short:

  • dc defines a constant: it writes the values you list into memory, right where the line is. dc.b, dc.w and dc.l write bytes, words and longs.
  • ds defines storage: it reserves room for a number of them and writes nothing. ds.l 6 is six longs of space, and what is in them is whatever was there before.
  • equ gives a number a name, and org says at which address what follows it goes.

So numbers: dc.w 4, 8, 15 puts three words in memory and calls the address of the first one numbers, and buffer: ds.b 64 reserves 64 bytes and calls the first one buffer.

An array

An array in C is a run of elements of the same size, one after another, and a[i] is the address of a[0] plus i times the size of an element. Assembly has exactly the same thing without the brackets: you keep the address of an element in a register and move it along by the size.

This one reserves six longs and writes 0 to 5 into them. Build it, type 1020 in the memory panel's address box, then Run.

Before you run it the six longs read FFFFFFFF, which is what untouched M68K memory reads as, and that is the point of ds: it reserved the room and wrote nothing. Afterwards:

addressvalue
0x102000000000
0x102400000001
0x102800000002
0x102C00000003
0x103000000004
0x103400000005

a0 ends at 00001038, four bytes past the last one, which is how the loop knew it had moved six times: it counted with d0 instead of comparing addresses. The add.l #4, a0 is the p++ of C written out, because C hides the size of what a pointer points at and assembly does not.

A string

A string is an array of bytes holding character codes. To the CPU 'H' is the number 72, or $48, which is what ASCII assigns to that letter, and nothing anywhere marks that byte as a letter rather than a number.

Nothing records how long a string is either, so something has to say where it ends, and the convention C uses is a byte of 0 after the last character. message: dc.b 'Hi!', 0 puts four bytes in memory:

addressbyteas a character
message48H
message + 169i
message + 221!
message + 300the terminator

Which is why counting the characters of a string is a loop that reads bytes until it reads a zero, and why a string of three letters takes four bytes.

The same thing in RISC-V

RISC-V and MIPS put their data in a .data section, and the directives are dotted words instead of dc and ds:

  • .byte, .half and .word write 1, 2 and 4 byte values, the way dc.b, dc.w and dc.l do.
  • .string writes a string with a 0 after it (.ascii writes one without).
  • .space reserves that many bytes, like ds.b.

This one counts the characters of message and stores the answer in length. Build it and Run: the memory panel already starts at 0x10010000, which is where the data section begins.

t1 comes out at 3 and memory looks like this:

addressbyteswhat it is
0x1001000048 69 21 00message, the same four bytes
0x1001000403 00 00 00length, the 3 the program just wrote
0x100100080A 00 00 00numbers[0], which is 10
0x1001000C14 00 00 00numbers[1], which is 20
0x100100101E 00 00 00numbers[2], which is 30
0x1001001401 02 03flags, three single bytes
0x1001001700 00 ...buffer, eight bytes nobody wrote

The words are written backwards because RISC-V is little endian: the word 10 is 0000000A and its lowest byte, 0A, sits at the lowest address. The M68K array above is stored the other way round, 00 00 00 01 for the 1, because the M68K is big endian, which is why that table could show each long as one number and this one has to show bytes. The bytes of message come out the same in both, since each of them is a byte on its own and there is nothing to reverse.

Press the text button in the memory panel's corner and the bytes are drawn as characters instead of hexadecimal, so message reads Hi! and the words next to it read as nonsense, which is a fair picture of what memory is: bytes, and whatever you decided they mean.

Try changing .string "Hi!" to .string "Hello" and running again. length comes out at 5, and everything after the string has moved: "Hello" takes six bytes, so length is at 0x10010008 and numbers[0] at 0x1001000C. Nothing in the program had to change, because it asked the assembler for those addresses by name.