CO Lab Manual
Course Page
  • Course Information
    • Welcome
    • Introduction
    • Your Contributions
    • Lab Sessions and Etiquette
    • Team Setup
    • Assumed Prior Knowledge
  • Setup Guides
    • GitHub Repository Setup
    • Technical Setup
      • Windows
      • Linux
      • macOS
    • GitHub SSH Setup
    • Framework Setup
  • Reference Documentation
    • Introduction to the Documentation
    • A Brief History Lesson
    • Syntax (Intel vs. AT&T)
      • Section Exercises
    • Memory
      • Memory Management
      • Section Exercises
    • Registers
      • Section Exercises
    • Instructions
    • Subroutines
      • Calling Subroutines
      • Writing Subroutines
      • Section Exercises
    • Input/Output
      • Printing to the Terminal
      • Reading from the Terminal
      • Section Exercises
    • Programming Constructs
    • Assembler Directives
    • C/C++ vs Assembly
    • Building and Running Programs
    • Address Sanitization
    • A0: A Running Example
  • Assignments
    • Introduction to the Assignments
    • Mandatory Assignments
      • A1: Subroutines and I/O
      • A2: Recursion
    • Extra Assignments
      • A3-a: Fibonacci Calculator
      • A3-b: Fibonacci REPL
      • A4: Diff
      • A5: Printf
      • A6: HPC
      • A7: Bitmap
      • A8: Game
  • Appendix
    • Acknowledgments
    • Rules and Regulations
    • Frequently Asked Questions
    • How to use a Debugger
Powered by GitBook
On this page
  • Format String
  • Example: Simple Printf Call
  • Additional Arguments
  • Example: Printf with Additional Arguments
  1. Reference Documentation
  2. Input/Output

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 (Linux man page: man 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!
PreviousInput/OutputNextReading from the Terminal

Last updated 4 months ago