This project has been created as part of the 42 curriculum by rfoo.
In this project, we will develop ft_printf, a custom implementation of the standard C library function printf. The goal is to reproduce its core functionality by parsing format strings and handling variable arguments, while gaining a deeper understanding of variadic functions, modular design, and low‑level output handling.
The ft_printf function supports a subset of the standard C printf conversion specifiers. Each specifier corresponds to a particular type of argument and output format:
| Specifier | Description |
|---|---|
| %c | Prints a single character |
| %s | Prints a string (as defined by the common C convention) |
| %p | Prints a void * pointer argument 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 |
To keep the implementation modular, ft_printf uses a dictionary structure that maps each format specifier to a corresponding handler function.
Stores an array of entries, each containing a specifier key (e.g. 'd', 's') and a function pointer.
Provides dict_set to register handlers and dict_get to retrieve them during parsing.
Ensures that each specifier is linked to the correct output logic.
Given the small and fixed number of specifiers that this version of ft_printf handles, a simple array of dictionary entries is more efficient and easier to manage than a linked list.
This design offers several advantages:
- Constant-time lookup: Iterating over a small array of specifiers is fast and predictable.
- Simplicity: No need for dynamic memory allocation or pointer manipulation as in a linked list.
- Readability: The mapping between specifiers and handlers is clear and compact.
- Fixed size: Since the set of supported specifiers is known in advance, an array is sufficient and avoids the overhead of a more complex data structure.
Each handler is a function that knows how to process one specific type of argument.
handle_int→ prints signed integershandle_uint→ prints unsigned integershandle_str→ prints stringshandle_ptr→ prints pointer addresses
All handlers share the same function signature (int handler(va_list *args)), so they can be stored uniformly in the dictionary.
Run:
make
This compiles all source files into object files and archives them into a static library, libftprintf.a.
We first implement a main.c function to test ft_printf.
#include <stdio.h>
#include <limits.h>
#include "ft_printf.h"
int main(void)
{
printf("%d\n", printf(NULL));
printf("%d\n", ft_printf(NULL));
printf("%d\n", printf(0));
printf("%d\n", ft_printf(0));
ft_printf("Character: %c\n", 'A');
ft_printf("Hello %s!\n", "world");
ft_printf("Pointer: %p\n", (void *)&main);
ft_printf("Decimal: %d\n", INT_MIN);
ft_printf("Integer: %i\n", INT_MIN);
ft_printf("Decimal: %d\n", 0);
ft_printf("Integer: %i\n", 0);
ft_printf("Unsigned: %u\n", 3000000000u);
ft_printf("Hex lowercase: %x\n", 255);
ft_printf("Hex uppercase: %X\n", 255);
ft_printf("Percent sign: %%\n");
ft_printf("Trailing percent sign: %\n", "percent sign");
ft_printf("Unknown specifier: %b\n", "unknown specifier");
return (0);
}
Then run the following command:
cc -Wall -Wextra -Werror -o my_program main.c libftprintf.a
Then, to run test our main.c function:
./my_program