*This project has been created as part of the 42 curriculum by dgaillet.* # ft_printf ## Description `ft_printf` is a reimplementation of the libc `printf()` function, packaged as a static library (`libftprintf.a`). The goal of the project is to learn how variadic functions work in C (`va_start`, `va_arg`, `va_end`) and to build code that is easy to extend with new conversions and flags. ```c int ft_printf(const char *format, ...); ``` It writes to standard output and returns the number of characters it printed. It returns `-1` on a write error or an invalid format. ### Supported conversions (mandatory) | Conversion | Output | |---|---| | `%c` | a single character | | `%s` | a string (`(null)` for a `NULL` pointer) | | `%p` | a pointer in hexadecimal, with a `0x` prefix (`(nil)` for `NULL`) | | `%d` / `%i` | a signed decimal integer | | `%u` | an unsigned decimal integer | | `%x` / `%X` | an unsigned integer in lowercase / uppercase hexadecimal | | `%%` | a literal `%` | ### Supported flags (bonus) The same implementation handles both the mandatory conversions and the bonus flags, so there is only one version of `ft_printf`. - Minimum field width, for example `%10d` - `-`: left-justify within the field - `0`: pad numbers with zeros (ignored when `-` or a precision is given) - `.`: precision (minimum digits for numbers, maximum characters for `%s`) - `#`: `0x` / `0X` prefix for non-zero `%x` / `%X` - `+`: always print a sign for signed numbers - ` ` (space): print a space in front of positive signed numbers Any combination of these flags behaves like glibc's `printf`. ## Instructions ### Compilation ```sh make # builds libftprintf.a (conversions and flags) make bonus # same as make: the bonus is built into the library make clean # removes object files make fclean # removes object files and libraries make re # fclean + all ``` The `Makefile` first builds `libft` with its own `Makefile`, then adds the `ft_printf` objects to a copy of `libft.a`. This way, `libftprintf.a` contains everything it needs. ### Usage ```c #include "ft_printf.h" int main(void) { int len; len = ft_printf("Hello %s, you are %d years old (%#x)\n", "Bob", 42, 42); ft_printf("%d characters printed\n", len); return (0); } ``` ```sh cc main.c -Iinclude -L. -lftprintf -o demo && ./demo ``` ## Algorithm and data structures ### Main loop Before printing anything, `ft_printf` checks the whole format string with `ft_check_format`. If a `%` is not followed by a conversion (for example, `"abc%"` or `"%5"`), it returns `-1` without writing anything, like glibc. This is not buffering: once the format is valid, output is still written as soon as it is ready, as the subject requires. `ft_printf` then reads the format string one character at a time: 1. An ordinary character is written right away with `write`. 2. A `%` starts a conversion specifier. The specifier is parsed into a `t_arg` structure, printed by the matching `print_*` function, and then skipped in the format string. 3. Every printing function returns how many characters it wrote, and `ft_printf` adds them up. ### Data structure Each conversion is described by a `t_arg` structure, which is allocated for that conversion and freed once it has been printed: ```c typedef struct s_arg { char arg; /* conversion character: c s p d i u x X % */ int padding; /* minimum field width */ int minus; /* '-' flag: width for left justification */ int zero; /* '0' flag: width to fill with zeros */ int dot; /* precision */ int hash; /* '#' flag */ int space; /* ' ' flag */ int plus; /* '+' flag */ } t_arg; ``` Every field starts at `-1`, meaning "not set". This keeps "not given" (`-1`) separate from "given as zero" (`0`), which matters for things like `%.0d`. A plain conversion such as `%d` leaves every flag at `-1`, so the mandatory conversions follow the same path with no special case. ### Processing a conversion 1. **Find the conversion** (`ft_main_arg`): skip flag, width and precision characters (`-0# +.` and digits). The first other character is the conversion. 2. **Parse** (`ft_parsing`): walk the specifier and fill in the `t_arg`. Each flag stores the number written right after it, the precision is read after the `.`, and the width is read backwards from the `.` or the conversion character. If `-` is present, `0` is ignored, as the C standard requires. 3. **Print** (`print_*`): `ft_print_arg_flags` fetches the argument with `va_arg` using the matching type. Each `print_*` function works out its own padding, in this order: leading spaces, then the sign or `0x` prefix, then zeros (from the `0` flag or the precision), then the digits. A trailing `-` field is padded with spaces at the end, in `ft_print_arg_flags`. 4. **Skip** (`ft_to_skip`): move the format index past the whole specifier. ### Printing numbers Numbers are printed by the recursive functions `ft_putnbr_base` (for `unsigned int`) and `ft_putnbr_base_ll` (for 64-bit pointers). They first recurse on `nbr / base_size`, then write `base[nbr % base_size]`, so the digits come out in the right order without a temporary buffer. The same functions print decimal, lowercase hexadecimal and uppercase hexadecimal, just by changing the base string. A negative `int` is turned into its absolute value as an `unsigned int` before printing, so `INT_MIN` works without overflow. ### Error handling On failure, every printing function returns a negative value (`-1` from `write`, or a large negative sentinel from the helpers). After each step, `ft_printf` checks whether the running total went down. If it did, `ft_printf` stops and returns `-1`. A conversion that prints nothing, such as `%s` with `""` or `%.0d` with `0`, adds 0 and is not an error. ### Why this design This layout has one small parser and one function per conversion. Supporting a new flag means adding a field to `t_arg`, a case in the parser and the related padding logic, without changing the main loop. ## Resources - [`printf(3)` man page](https://man7.org/linux/man-pages/man3/printf.3.html): how flags, width and precision behave - [`stdarg(3)` man page](https://man7.org/linux/man-pages/man3/stdarg.3.html): variadic functions - [cppreference: `printf`](https://en.cppreference.com/w/c/io/fprintf): the full conversion specification - The C standard (ISO/IEC 9899), section 7.21.6.1, on the `fprintf` function - The 42 Norm, for code style ### Use of AI The implementation was written by hand. An AI assistant (Claude) was used at the end of the project to: - **Review the code against the subject and the real `printf`.** It wrote a test program that compares the output and return value of `ft_printf` with glibc's `printf` on many edge cases (empty strings, `NULL`, `INT_MIN`, zero precision, flag combinations, trailing `%`). It also ran the tests with AddressSanitizer and UBSan. - **Fix the bugs it found, with minimal changes.** These were: empty output being treated as an error, field width being lost when combined with `#`/`+`/space, `0` not being ignored with `-`, the `#` prefix on zero, and signed overflow on `INT_MIN`. - **Merge the mandatory and bonus implementations into a single version**, and check the format before printing so an incomplete `%` returns `-1` without any output. - **Write this README.** Every change was reviewed and understood before it was kept.