AArch64 Playground
4.26 · Strings, pointer arrays, and argv

Strings, pointer arrays, and argv

To a program, text is nothing but bytes in memory. This lesson reads and changes text one byte at a time, builds a table of strings, and reads the words typed after a program's name on the command line.

Characters are numbers

A computer stores text as numbers, one per character. The table that pairs each character with its number is ASCII (American Standard Code for Information Interchange): 'A' is 65, 'a' is 97, '0' is 48, a space is 32 and a newline is 10. Every code fits in one byte.

You never need to look the numbers up. A character constant, one character between single quotes, stands for its code, so mov w10, 'a' puts 97 in w10 and cmp w10, 'z' compares with 122. Arithmetic works on them too: 'a' - 'A' is 32, the distance from any capital letter to its lower-case partner.

The letters come in order, so 'a' to 'z' is one unbroken range, and so are 'A' to 'Z' and '0' to '9'. A digit character is not its value: '7' is 55, and subtracting '0' leaves 7.

A string is bytes ending in zero

A string is a run of character codes in consecutive bytes, followed by one byte holding 0, called the terminator (or null byte). The label on a string is the address of its first byte. Nothing records the length, so any code that walks a string, printf's %s included, reads bytes until it meets the 0.

Three directives put text in memory:

DirectiveWhat it stores
.string "hi"3 bytes: 'h', 'i', 0
.asciz "hi"the same 3 bytes; another spelling of .string
.ascii "hi"2 bytes: 'h', 'i', and no terminator

Text made with .ascii has no end marker, so %s runs on into whatever follows it in memory. Use .string unless you are joining pieces by hand.

Walking a string one byte at a time

ldrb loads one byte into a register and fills the rest of the register with zeros; strb stores the lowest byte of a register. Together they let a program read and change a string one character at a time.

The program below points x9 at the first byte of a note. For each byte before the terminator, it checks whether the byte lies between 'a' and 'z'. If it does, subtracting 32 turns it into the matching capital, and strb writes it back in the same place. The note is in .data, so the program is allowed to change it. The loop loads each byte in its test, so an empty string, which is only a terminator, runs the body zero times. It prints:

before: Meet me at 9, by the old oak.after:  MEET ME AT 9, BY THE OLD OAK.
loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 1try it: run it, or step one instruction at a timeOpen in playground

An array of pointers to strings

Strings differ in length, so they cannot sit in a table where every entry has the same size. Their addresses can, since every address is 8 bytes. .dword stores 8-byte values, and a label written as a value stands for its address, so this line builds a table of four addresses, 32 bytes in all:

phrases_m:  .dword hello_m, bonjour_m, hola_m, salaam_m

Entry i starts at phrases_m + i * 8, so the index is scaled by 8: ldr x2, [table_r, pick_r, SXTW 3] (a left shift by 3 multiplies by 8). What comes back is an address, ready for %s, and one ldrb through it reads the first letter. .balign 8 in front of the table starts it at an address that is a multiple of 8.

With PICK set to 2 the program prints phrase 2 is hola, first letter h. Change the define to 0, 1 or 3 and run it again.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 2try it: run it, or step one instruction at a timeOpen in playground

argc and argv

When a program starts, the operating system passes main two arguments:

int main(int argc, char *argv[]);

argc, in w0, counts the words on the command line, the program's own name included. argv, in x1, is the address of an array of pointers, one per word, built just like phrases_m: argv[0] points at the program's name, argv[1] at the first argument, and so on. After the last one sits an entry holding 0.

Every argument is a string, even one that looks like a number: 42 arrives as the bytes '4', '2', 0. The library function atoi turns a string like that into an int.

The program below prints argc, then walks argv from the last entry down to argv[1], printing each argument and its length. The length comes from strlen, a library function that counts the bytes before the terminator. The walk stops before argv[0], whose text depends on how the program was started. argc, argv, the index and the current argument all have to survive calls to printf and strlen, so they live in x19 to x22, which main saves first.

The lesson runs it with the arguments red green blue, so it prints:

argc = 4argv[3] = blue, length 4argv[2] = green, length 5argv[1] = red, length 3
loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 3try it: run it, or step one instruction at a timeOpen in playground

To try other words, open the program in the playground and change the args box there. With the box empty, argc is 1 and the loop body never runs.

pitfall

A common mistake from this lesson, with a broken program and its fix that you can run:

Check yourself

  1. What does .string "ok" store, byte by byte?
  2. x9 holds argv. Which instruction loads argv[2], the address of the second argument's text?
  3. A program reads the character '7' out of a string and wants to add seven to a total. What must it do first?
  4. A program is started as ./a.out one two. What is argc?

answers

show answers
  1. Three bytes: 111 ('o'), 107 ('k') and the terminator 0.
  2. ldr x10, [x9, 16]. Entry 2 starts 2 times 8, or 16, bytes into the array.
  3. Subtract '0'. The byte holds 55, the code for '7', and 55 - 48 is 7.
  4. 3: the program's name and the two arguments.

Practice