Strings, pointer arrays, and argv
prerequisite
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:
| Directive | What 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.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.
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 3To 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
- What does
.string "ok"store, byte by byte? x9holdsargv. Which instruction loadsargv[2], the address of the second argument's text?- A program reads the character
'7'out of a string and wants to add seven to a total. What must it do first? - A program is started as
./a.out one two. What isargc?
answers
show answers
- Three bytes: 111 (
'o'), 107 ('k') and the terminator 0. ldr x10, [x9, 16]. Entry 2 starts 2 times 8, or 16, bytes into the array.- Subtract
'0'. The byte holds 55, the code for'7', and 55 - 48 is 7. - 3: the program's name and the two arguments.
Practice
- Count the vowels: walk a string byte by byte.
- Caesar's little secret: change a string in place.
- Palindrome or not: compare a word from both ends, capitals and all.
- argv arithmetic: add up numbers given on the command line.
- Say it louder: print every argument in capitals.
- Morse code translator: look each letter up in a table of string pointers.
- Basic quiz: strings, Fill in the blank: strings (core) and Predict: strings (challenge): character codes, the zero byte at the end, and loading and storing one byte.
- Basic quiz: command-line arguments and Predict: command-line arguments (challenge): what
argccounts, where eachargventry sits, and whatatoimakes of an argument.