Demystifying printf

Algozenith
4 min readJan 3, 2022

--

Have you ever wondered how printfworks in C? How it could be able to take a variable number of arguments? In this article, we’ll spend some time understanding our favourite printf.

First, let’s look at the declaration of the function.

int printf ( const char * format, ... );

The printf writes the C string pointed by format to the standard output (stdout). If format includes format specifiers (subsequences beginning with %), the additional arguments following format are formatted and inserted in the resulting string replacing their respective specifiers. For more details, you can look into the documentation.

Notice ... in the declaration. This represents that the function will be having a variable number of arguments. We’ll learn about this later part of the article.

AlgoTwister

Knowing the declaration of printf, let’s try to answer the AlgoTwister 8:

AlgoTwister 8

There are a lot of things happening here. printf() doesn't 'know' anything about the expression 6 + "Hello AlgoZenith". The value of that expression is determined by the C language.

Firstly, a + b is the same as b + a, so 6 + "Hello AlgoZenith" is the same as "Hello AlgoZenith" + 6.

Notice that the first argument of printf is the pointer to the character array. Now, the type of "Hello AlgoZenith" (i.e., a string literal) is an "array of char". Specifically, "Hello AlgoZenith" is a 17-character array (16 "regular" characters, followed by a \0). When used in most expressions, the type of an array in C "decays" to a pointer to its first element, and binary addition is one such case. All this means that in "Hello AlgoZenith" + 6, "Hello AlgoZenith" decays to a pointer to its first element, which is the character H.

The value of the address of H plus 6 is a pointer that points to 6 locations from H above, which is A. So, printf() is getting an address that is at A. printf() prints that till it finds a \0. Hence you see AlgoZenith it as output.

To understand the working of printf at the fullest, we first need to understand what Variadic functions are.

Variadic Functions

Variadic functions are functions that take a variable number of arguments. The declaration of a variadic function uses an ellipsis ...as the last parameter, e.g. see the declaration of printf function.

Variadic functions have two types of arguments:

  1. Mandatory Arguments
  2. Optional/Variadic Arguments

Look at the printf examples.

printf Examples

We can observe that the first parameter const char * is the mandatory one, and the others are optional in printf function.

There can be more than one mandatory parameter. But mandatory parameters should appear before optional parameters in the list, i.e., we need to declare them before the ellipsis ... in the function declaration.

Accessing the variadic arguments (optional arguments) from the function body uses the following library facilities which reside in <stdarg.h>

  1. va_listis used in situations in which we need to access optional parameters and it is an argument list. So, our list will contain some data that will be accessed after we declare our va_list.
  2. va_start will connect our argument list with a user-defined pointer, let’s denote it as someArgumentPointer. There must be at least one named argument; the final named argument is used by va_start to get started.
  3. Each call of va_arg returns one argument and steps someArgumentPointer to the next; va_arg uses a type name to determine what type to return and how big a step to take.
  4. Finally, va_end does whatever cleanup is necessary. It must be called before the program returns.

Let’s solidify our understanding with the following example. The sum function does the sum of elements passed to it as arguments. The information about the number of elements to be added is given by the first parameter of the function.

Variadic sum function
  1. Line 4: The first argument of the function is numOfElements, which is a mandatory argument and contains the information of how many elements we’re adding.
  2. Line 4: ...means that the other parameters are variadic (optional).
  3. Line 6: The list of the arguments are stored in va_list. We initialize someArgumentPointer with the list in order to access the list.
  4. Line 10: va_start connects our someArgumentPointer to our variadic list.
  5. Line 14: va_arg returns the argument in curElement and increment the someArgumentPointer, which will after point to the next element in the list.
  6. Line 15: We add our curElement of the list in the sumOfElements variable.
  7. Line 18: We end our traversal in the variadic arguments’ list.

Now let’s come back to our printf. We are now able to understand how printf can able to process the variable number of arguments.

With these understandings, let’s implement our own version of printf, let’s call it as simple_printf.

Here’s the implementation:

simple_printf function

--

--

No responses yet