Printing to the Terminal

The C Standard Library contains a function called printf, which can be used for printing to the terminal (stdout). You can call the function directly from your program and the linker will make sure the call goes to the actual subroutine once your program is built.


Format String

The function takes a variable number of arguments, but at least one. The first argument is always an ASCII string, commonly called the format string. A string is simply an array of characters, terminated with a zero byte.

As you may have noted, arguments are passed to a subroutine through 64-bit registers. So how do you fit a whole string in a register? You don't. Instead, printf expects the first argument to be a memory address specifying the location of the first byte of a string.

In Assembly, you can define a string as part of your .text section with the assembler directive .ascii (not zero terminated) or .asciz (zero terminated). The address of that location can then be retrieved using the leaq (Load Effective Address) instruction.

Example: Simple Printf Call

The following call in a C program:

printf("Hello world!\n);

would look something like this in Assembly:

.text
hello_world: 
    .asciz "Hello world!\n"

main:
    # ...
    leaq    hello_world(%rip), %rdi
    movb    $0, %al
    call    printf

Let's break down what is happening here:

  • .text: this directive tells the assembler that the following lines should be placed in the text section of the program - remember that the text section is a read-only section mainly holding the program instructions

  • hello_world: this is simply a label, such that the position in the code can be referred to by name - any usage of the label will be replaced by the actual address by the assembler

  • .asciz: this directive tells the assembler that the following string should be placed in the text section as ASCII-encoded characters, followed by a zero byte

  • leaq hello_world(%rip): this instruction will load the address of the hello_world label into the register rdi - so as the first argument for printf

  • movb $0, %al: as printf takes a variable number of arguments, the number of vector registers used (e.g., for floating point values) needs to be specified - in the case of the mandatory assignments, this will always be 0.

You may note that the instruction to load the address looks a bit confusing. Specifically the use of (%rip). This is needed due to something called Position-Independent Executables (PIE). The full explanation goes beyond the scope of this Manual, however, the gist is that your program may be loaded at different memory addresses (especially in the context of shared libraries). Thereby, the address of the string is specified relative to the instruction pointer, as the relative distance between the two will be the same no matter where in memory the program is.


Additional Arguments

In addition to "simple" printing, printf can further be used to print strings that include variables, which are passed as additional arguments. To indicate where in the string such variables should be placed, printf has so-called format specifiers. The most commonly used format specifiers are:

  • %d/%u: signed/unsigned integer (32 bits on most systems)

  • %ld/%lu: signed/unsigned long (64 bits on most systems)

  • %x/%lx: hexadecimal integer/long

  • %f: floating point number (even though %lf also exists, %f equally refers to double-precision floats in most implementations)

  • %s: zero-terminated ASCII string

Anytime such a format specifier is encountered in the format string, printf will take the next argument and substitute its string representation into the final output.

Example: Printf with Additional Arguments

The following call in a C program:

printf("It is %u:%u o'clock!\n", 9, 41);

would look something like this in Assembly:

.text
time_fmt: 
    .asciz "It is %u:%u o'clock!\n"

main:
    # ...
    leaq    time_fmt(%rip), %rdi
    movl    $9, %esi
    movl    $41, %edx
    movb    $0, %al
    call    printf

and produce the following output:

It is 9:41 o'clock!

Last updated