Preface
This essay is a documentation of my learning process. It details how I went about engineering my own ft_printf()
.
- The task was to write a
ft_printf()
à la the originalprintf()
in C - The function has to handle the following conversions: cspdiuxX%
- I did not attempt the optional bonus assignments for this project.
Process
01. Understanding how the original printf()
works
02. Researching the key concept required: Variadic function
03. Thinking modularly and creating a plan
04. Coding, testing, debugging and learning from failure
🏃🏻♂️Start day:
09 October 2023
🔚 Submission day:
31 October 2023 first submission failed ❌,
03 November 2023 second submission 100/100 ✅
Duration:
🗓 26 days
🕓 62.61 hours total time spent on campus.
👦🏻 Many more hours spent working from home, after the children are asleep, till 1am to 2am
👨🏻💻 C. Files submitted: Makefile
ft_countdigits.c
ft_printf.c
ft_printf.h
ft_putchar.c
ft_puthex_fd.c
ft_putnbr_fd.c
ft_putpointer.c
ft_putstr.c
ft_putu.c
01. Understanding how the original printf() works
The original printf()
, included in the <stdio.h>
library, is one of the main C output functions. Printf stands for “print formatted“. The prototype of my ft_printf()
is: int ft_printf(const char*, ...);
Formatting takes place via placeholders (%) within the format string. Format specifiers follow after placeholders. Eg. %s
prints a string.
Below are the details of the conversion that was implemented in ft_printf()
:
• %c
Prints a single character.
• %s
Prints a string (as defined by the common C convention).
• %p
The void * pointer argument has to be printed in hexadecimal format.
• %d
Prints a decimal (base 10) number.
• %i
Prints an integer in base 10.
• %u
Prints an unsigned decimal (base 10) number.
• %x
Prints a number in hexadecimal (base 16) lowercase format.
• %X
Prints a number in hexadecimal (base 16) uppercase format.
• %%
Prints a percent sign.
In C, printf
returns the count of characters displayed as well.
Eg: #include <stdio.h>
int main()
{
int count;
count = printf("Hello world\n");
printf("%d", count);
return (count);
}
Besides printing Hello World, the above code snippet will return 12 as well. (‘hello’ – 5 characters, ‘world’ – 5 characters, space – 1 character and newline(‘\n’) – 1 character). Therefore it is important to keep track of the count
when engineering a solution.
02. Researching the key concept required: Variadic function
The <stdarg.h>
is a header in the C standard library of the C programming language that allows functions to accept an indefinite number of arguments. The ellipsis ...
in the ft_printf prototype indicates that a variable number of arguments of any type will be accepted. For example: ft_printf("Color %s, Number %d", "red", 123456)
.
<stdarg.h> includes the following methods:
Methods | Descriptions | |
---|---|---|
va_start(va_list ap, argN) | This enables access to variadic function arguments. where *va_list* will be the pointer to the last fixed argument in the variadic function*argN* is the last fixed argument in the variadic function. From the above variadic function (function_name (data_type variable_name, …);), variable_name is the l ast fixed argument making it the argN. Whereas *va_list ap* will be a pointer to argN (variable_name) |
|
va_arg(va_list ap, type) | This one accesses the next variadic function argument. *va_list ap* is the same as above i.e a pointer to argN *type* indicates the data type the *va_list ap* should expect (double, float, int etc.) |
|
va_copy(va_list dest, va_list src) | This makes a copy of the variadic function arguments. |
|
va_end(va_list ap) | This ends the traversal of the variadic function arguments. |
source: GeeksforGeeks
The basic structure of my ft_printf()
is as such. It has taken me some time to understand how variadic function actually works.
int ft_printf(const char *format, ...)
{
va_list argument_pointer;
int count; va_start(argument_pointer, format);
count = 0;
if (!format)
return (0);
while (*format != '\0')
{
if (*format == '%')
count += print_convert(*(++format), argument_pointer);
else
count += ft_putchar(*format);
format++;
}
va_end(argument_pointer);
return (count);
}
03. Thinking modularly and creating a plan
When working on this ft_printf(),
I surveyed the requirements broadly and broke down the projects into smaller manageable modules. I practiced modular thinking¹, and tried as much as I could to reuse codes which I had written for the previous Libft project – treating codes as modular building blocks. A basic plan to print a string for example would be like this:ft_printf()
|
ft_putstring()
|
ft_putchar()
The modular thinking approach helped reduce the project’s requirements to irreducible basic first principles. From these foundational first principle substructures, I rebuilt the structures and the superstructures of my ft_printf().
I also figured that the cspdiuxX%
specifier worked as a kind of flag that could call out different functions to handle different conversions. The blueprint of my project’s functions looked like this:ft_print()
|__ ft_putchar()
|__ ft_printconvert()
|__ ft_putchar()
|__ ft_putstr()
| |__ ft_putchar()
|__ ft_puthex()
|__ ft_putnbr_fd()
|__ ft_putpointer()
| |__ ft_pointertohex()
|__ ft_putu()
|__ ft_countu()
It is in ft_printconvert()
that the magic happens✨:
ft_putchar.c
: The function that handles the %c specifier and the %% specifier.ft_putstring.c
: The function that handles the %s specifier.ft_puthex_fd.c
: a single conversion function that handles both %d and %i specifiers.ft_putpointer.c
: The function that handles the %p specifier.ft_putu.c
: The function that handles the %u specifier.ft_puthex_fd.c
: The function that handles the %x and %X specifiers.
04. Coding, Testing, Debugging, and Learning from failure
It took me two attempts to pass this project. The counter for the code did not work correctly for the first submission. Also, I have missed implementing the function to handle %u
. The test I wrote was also not rigorous enough to pick up on the bugs in the function. I really enjoyed myself working on this project. Below is the test I had written for ft_printf();
int main(void)
{
int x;
int y; int count;
int c;
int number;
int hex;
int limit;
char str[] = "This is a string";
long unsigned address;
address = 42;
limit = -2147483647;
hex = 255;
number = 12345;
c = 'a';
count = ft_printf("hello %s\n", "jack");
ft_printf("The chars written are %d\n", count);
printf("char character using printf %c\n", c);
ft_printf("char character using ft_printf %c\n", c); printf("%c%c%c\n", 'a', '\t', 'b');
ft_printf("%c%c%c\n", 'a', '\t', 'b');
count = ft_printf("%s\n", str);
ft_printf("chars written using ft_printf %d\n", count); printf("string using printf %s\n", str);
printf("printf percent sign %%%%%%\n");
ft_printf("ft_printf percentage sign %%%%%%\n");
printf("prinft %d\n", number);
ft_printf("ft_printf %d\n", number);
printf("printf %d\n", limit);
ft_printf("ft_printf %d\n", limit);
printf("printf %i\n", number);
ft_printf("ft_printf %i\n", number);
printf("printf value in hexadecimal: %x and %X\n", hex, hex); ft_printf("ft_printf value in hexadecimal: %x and %X\n", hex, hex); printf("printf %p\n", (void *)&address);
ft_printf("ft_printf %p\n", (void *)&address);
x = printf("%c %s %p %d %i %u %x %X %%\n", c, str, &str, number, hex, number, number, hex);
y = ft_printf("%c %s %p %d %i %u %x %X %%\n", c, str, &str, number, hex, number, number, hex);
printf("%d %d", x, y);
}