C printf: formatted output without cout

CS 301: Assembly Language Programming Lecture, Dr. Lawlor

The standard C library function printf (print with formatting) is a very commonly used function to get output from plain C, which doesn't have cout.  The basic idea is the first argument is a "format string", which printf just copies as-is to the output until it finds a percent sign, which is treated as a "format specifier", with a bewildering array of possible data types and formats.  

For example, I can print an integer in decimal by adding the format specifier "%d" in the string:

int i;
for (i=1;i<=20;i++)
	printf(" now i=%d\n",i);

(Try this in NetRun now!)

Changing the format specifier results in wildly different output for the same integer:

Format Specifier Example Output Description
%d  now i=15 Decimal output.
%8d  now i=      15 Decimal with minimum output width.
%08d  now i=00000015 Include leading zeros
%X  now i=F Hexadecimal output

Because the format string mixes literal things to print with the format specifiers, there are some weird corner cases.  You print a bare "%" using the format specifier "%%" (similar to how you can print an actual \ using "\\").  The problem is a bare percent sign can get interpreted as a format specifier, like this bad code:

printf("Progress: 100% done\n");

(Try this in NetRun now!)

Because the "% d" is interpreted as a format specifier, this prints: Progress: 100 0one.  To print "100%", the format string is "100%%".

You can also print strings, using %s.  You pass a pointer to the string as the argument.

const char *str="OK";
if (bar==5) str="not so good";
printf(" status is %s\n",str);

(Try this in NetRun now!)

You can pass multiple format specifiers, mixing and matching string and integer data.  It is a little clunky that you need to pass all the parameters at the end--cout's way seems a little easier to keep organized.

int code=9;
const char *status="green";
printf("The status is %s and the return code is %d\n", status,code);

(Try this in NetRun now!)

What happens if you use the %s format, but pass an integer?  printf tries to treat the integer as a pointer, and crashes!  What happens if you use the %d or %X format, but pass a pointer?  printf will dump the value of the pointer (instead of its contents).  What happens if you pass any format specifier, but omit the argument?  printf will blindly print the garbage contents of that parameter register.  Just like assembly language, printf will do exactly what you ask it to, not what you might want it to do!  At least the compiler will warn you about a mismatch between format string and argument.

Go read the full printf spec now! 

You can also use printf-style format specifiers to write data to files with fprintf, or to write to a character array with snprintf.

Printf and Security

It's fairly common for lazy programmers to abuse printf as a general purpose "print this string" function, similar to puts.  The code looks like this:

const char *someString=bar.c_str();
printf(someString); // WARNING!  What if someString has % format specifiers?

(Try this in NetRun now!)

This is a surprisingly dangerous piece of code, because someString can contain format specifiers.  For example, if someString contains "%x_%x_%x", this will print the current values in the argument registers, which may contain secret information.  Adding more "%x" will eventually start printing the contents of the stack, and "%n" can be used to overwrite the stack, potentially allowing the source of the string to take over your machine!

The correct way to print a string is with printf("%s",someString).  Printf will then just copy the entire string, and ignore any % signs in someString.

Printf in Assembly

To call printf from assembly language, you just pass the format string in rdi as usual for the first argument, pass any format specifier arguments in the next argument register rsi, then rdx, etc.  There is one surprise: you need to zero out the al register (the low 8 bits of eax/rax), or else printf will assume you're passing it vector registers and crash.

mov rdi,formatStr ; first argument: format string
mov rsi,5 ; second argument (for format string below): integer to print
mov al,0 ; magic for varargs (0==no magic, to prevent a crash!)

extern printf
call printf

ret

formatStr:
	db `The int is %d\n`,0

(Try this in NetRun now!)

This is a very common way to get output from assembly language!