Printing a string with printf
prerequisite
Read this first:
This lesson prints a string to standard output in assembly, which takes more work than a print statement in a high-level language. Standard output is the stream of text a program writes as it runs; in the playground it appears in the console.
A high-level language has a built-in way to print. In C, it is the printf function:
#include <stdio.h>int main() { printf("Hello, World!\n"); return 0;}The string "Hello, World!" is passed to printf as an argument, and printf writes it to standard output.
In assembly, printing takes a few more steps.
The big picture
Assembly prints by calling the same C printf function. The first thing printf receives is its format string: the text to print, which can also hold placeholders for values (the next lesson covers those). To print a string:
- Store the string in a variable.
- Load the variable's address into the first argument register,
x0. - Call the
printffunction.
Storing the string
The first step in printing a string in assembly is to store the string in a variable. In assembly, a variable is a label, a name followed by a colon, that marks where a value sits in memory. The .data directive starts the data section, the part of the program that holds values rather than instructions, and the .string directive stores the string there. The following code does this:
.datagreeting: .string "Hello, World!\n"In the above code, the string "Hello, World!\n" is stored in a variable named greeting. The .string directive writes the characters followed by a zero byte, which is how printf knows where the text ends. The \n at the end is a single character, the newline, which moves any later output to a fresh line.
Loading the address
The next step in printing a string in assembly is to load the variable's address into the appropriate register. This is done using the ldr instruction with an = in front of the label. An address is 64 bits wide, too wide for any immediate, so the assembler stores it in a small table next to the code, called the literal pool, and ldr loads it from there. The following code does this:
ldr x0, =greeting // x0 = address of greetingThe address goes in x0 because x0 is the first argument register, which is where printf looks for the format string. printf receives the address of the string, not the characters themselves, and reads the characters from memory up to the zero byte.
Calling printf
The final step in printing a string in assembly is to call the printf function. This is done using the bl instruction, short for branch with link: it jumps to printf and stores the address of the next instruction in lr (x30), so printf knows where to come back to. The following code does this:
ldr x0, =greeting bl printfbl calls printf, which prints the string whose address is in x0.
The whole program
The following program puts the three steps together. The two lines at the start of main, the prologue, save fp and lr, because bl printf overwrites lr and main still needs it to return. The ldp before ret, the epilogue, puts them back. The stack lesson explains both line by line. mov w0, 0 sets the value main returns, which becomes the program's exit status: 0 means the run succeeded.
Run it and the console shows Hello, World!.
note
Because the string is a format string, printf treats a % in it as the start of a placeholder. To print a percent sign itself, write %%: the string "100%% done\n" prints 100% done.
Two calls, one line
printf prints exactly the characters it is given and adds nothing of its own, not even a newline. The program below prints two strings with two calls. The first string has no \n, so the second call carries on along the same line, and the output is Two calls, one line.
Add \n to the end of the first string and run it again: the output now spans two lines. Notice too that x0 is loaded again before the second call. printf may change x0, so the first address is not still there.
Check yourself
- What does
.stringadd after the characters you write, and why doesprintfneed it? - Which register holds the string's address when
printfis called? - One call prints
Loading...and the next printsdone, and the console showsLoading...done. What is missing?
answers
show answers
- A zero byte;
printfstops reading there. x0, the first argument register.- A
\nat the end of the first string.
Practice
- Intermediate quiz: ARMv8 assembly: asks what the argument registers are for.
- Warm up the registers: its starter ends with a
printfcall built the same way. - Say it with a frame: draw a box around a word with strings you add to
.data.