- 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 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.
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 field0: pad numbers with zeros (ignored when-or a precision is given).: precision (minimum digits for numbers, maximum characters for%s)#:0x/0Xprefix 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
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
#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);
}
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:
- An ordinary character is written right away with
write. - A
%starts a conversion specifier. The specifier is parsed into at_argstructure, printed by the matchingprint_*function, and then skipped in the format string. - Every printing function returns how many characters it wrote, and
ft_printfadds 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:
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
- Find the conversion (
ft_main_arg): skip flag, width and precision characters (-0# +.and digits). The first other character is the conversion. - Parse (
ft_parsing): walk the specifier and fill in thet_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,0is ignored, as the C standard requires. - Print (
print_*):ft_print_arg_flagsfetches the argument withva_argusing the matching type. Eachprint_*function works out its own padding, in this order: leading spaces, then the sign or0xprefix, then zeros (from the0flag or the precision), then the digits. A trailing-field is padded with spaces at the end, inft_print_arg_flags. - 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: how flags, width and precision behavestdarg(3)man page: variadic functions- cppreference:
printf: the full conversion specification - The C standard (ISO/IEC 9899), section 7.21.6.1, on the
fprintffunction - 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 offt_printfwith glibc'sprintfon 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,0not being ignored with-, the#prefix on zero, and signed overflow onINT_MIN. - Merge the mandatory and bonus implementations into a single version, and
check the format before printing so an incomplete
%returns-1without any output. - Write this README.
Every change was reviewed and understood before it was kept.