Demystifying printf
Have you ever wondered how printf
works 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:
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:
- Mandatory Arguments
- Optional/Variadic Arguments
Look at the 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>
va_list
is 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 ourva_list
.va_start
will connect our argument list with a user-defined pointer, let’s denote it assomeArgumentPointer
. There must be at least one named argument; the final named argument is used byva_start
to get started.- Each call of
va_arg
returns one argument and stepssomeArgumentPointer
to the next;va_arg
uses a type name to determine what type to return and how big a step to take. - 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.
- 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. - Line 4:
...
means that the other parameters are variadic (optional). - Line 6: The list of the arguments are stored in
va_list
. We initializesomeArgumentPointer
with the list in order to access the list. - Line 10:
va_start
connects oursomeArgumentPointer
to our variadic list. - Line 14:
va_arg
returns the argument incurElement
and increment thesomeArgumentPointer
, which will after point to the next element in the list. - Line 15: We add our
curElement
of the list in thesumOfElements
variable. - 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:
We hope this article helps you to understand our beloved printf
function.
References
- https://www.cplusplus.com/reference/cstdio/printf/
- https://stackoverflow.com/questions/3332611/how-is-printf-statement-interpreted
- https://en.cppreference.com/w/c/variadic
- https://www.thegeekstuff.com/2017/05/c-variadic-functions/
- https://www.hackerearth.com/practice/notes/know-our-printf-variable-number-of-arguments-to-a-function/
- https://stackoverflow.com/questions/1056411/how-to-pass-variable-number-of-arguments-to-printf-sprintf