Merge mandatory and bonus into a single ft_printf, fix edge cases, add README

- Use the flag-aware implementation for both make and make bonus
  (bonus/ moved to src/, _bonus suffix dropped, bonus rule = all)
- Do not treat zero-length output (%s with "", %.0d with 0) as an error
- Return -1 without printing when a % has no conversion ("abc%", "%5")
- Keep field width with '#', '+' and ' '; ignore '0' with '-'
- Fix '#' prefix on zero, '+'/' ' with zero padding, INT_MIN negation UB
- Add README.md required by the subject

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
da7eandClaude Opus 5.5 committed 2026-10-08 22:22:28 +02:00
1 parent b620f5ecdb
commit c8a9ee10cf
24 files changed
+608 -678

No files matched your search

+196
View File
@@ -0,0 +1,196 @@
*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.