Writing My Own Printf Funtion

Updated November 14, 2023

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 original printf()  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

Static Badge

Static Badge

🏃🏻‍♂️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()
%cPrints 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);
}


1. ‘modular thinking’ was a thing I thought about in my head. I totally didn’t know it was an actual thing! https://medium.com/igniterspace/modular-thinking-breaking-complicated-systems-down-into-simpler-pieces-534cbb72a047
Published November 13, 2023
Category: 42 Singapore SUTD
Tags: ,

Leave a Reply

Your email address will not be published. Required fields are marked *