mirror of
https://github.com/DavidGailleton/42-ft_printf.git
synced 2026-11-01 11:42:03 +01:00
- 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>
197 lines
7.3 KiB
Markdown
197 lines
7.3 KiB
Markdown
*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.
|