ft_printf
Build your own printf — variadic functions, format parsing, and output
ft_printf is the 42 project where a lot of things click, because you rebuild a function you've used a hundred times and discover it isn't magic — it's a loop over a string, a clever way to grab an unknown number of arguments, and a pile of small conversions. This subject teaches the two ideas the project actually hinges on (variadic functions, and format-string parsing), then hands you the spec. When you're done you'll have a printf you wrote, and you'll never look at the format string the same way again.
- › Explain how a function can take a variable number of arguments (and why that's possible)
- › Use stdarg (va_list, va_start, va_arg, va_end) correctly and safely
- › Parse a format string: walk it, detect conversions, and dispatch each one
- › Reimplement printf's core conversions and return the correct character count
1 · What printf really is
printf("%d and %s", 42, "hi") looks special, but strip away the mystery and it's this: a function
that takes a format string plus however many extra arguments, walks the format string one
character at a time, prints ordinary characters as-is, and when it hits a `%` it reads the next
argument and prints it in the requested form. It returns how many characters it printed. That's
the entire contract.
printf with 1 arg, or 5)? And (2) how do you walk the
format string and dispatch each %x, %d, %s… to the right handler? Solve those two and the rest
is small conversion functions you already know how to write.2 · Variadic functions (stdarg)
A function that takes a variable number of arguments is called variadic. In C you declare it with
... after the named parameters, and you reach the extra arguments through four tools from
<stdarg.h>:
- `va_list ap;` — a cursor that walks the extra arguments.
- `va_start(ap, last);` — point the cursor just after
last(the last *named* parameter). - `va_arg(ap, type);` — read the next argument *as
type* and advance the cursor. - `va_end(ap);` — clean up when you're done.
1#include <stdarg.h>2int sum(int count, ...) { // 'count' tells us how many follow3 va_list ap;4 va_start(ap, count); // start right after 'count'5 int total = 0;6 for (int i = 0; i < count; i++)7 total += va_arg(ap, int); // grab the next int, advance8 va_end(ap);9 return total;10}11// sum(3, 10, 20, 30) -> 60va_arg has no idea what type is actually there — *you* tell it, and you must be right. Ask for
va_arg(ap, int) when a char* is there and you get garbage or a crash. This is why printf needs the
format string: the %d / %s / %p is *how you know which type to pull* for each argument. The
format string and the arguments must line up — that's the contract between caller and printf, and it's
why a wrong format specifier is a real bug.3 · Parsing the format string
Now the loop. Walk the format string character by character. If it's an ordinary character, print it.
If it's a `%`, look at the *next* character — that's the conversion — pull the matching
argument with va_arg, convert it to text, and print that. Then continue past the conversion.
1int ft_printf(const char *fmt, ...) {2 va_list ap;3 va_start(ap, fmt);4 int count = 0; // characters printed so far5 for (int i = 0; fmt[i]; i++) {6 if (fmt[i] == '%') {7 i++; // move to the conversion char8 count += handle_conversion(fmt[i], ap); // dispatch on fmt[i]9 } else {10 count += write_char(fmt[i]); // ordinary char, print it11 }12 }13 va_end(ap);14 return count; // total characters written15}handle_conversion is a dispatch (an if/else chain or a small table — remember function pointers?):
'c' prints a char, 's' a string, 'd'/'i' a signed int, 'u' an unsigned, 'x'/'X' hex,
'p' a pointer, and '%' prints a literal %. Each handler pulls the right type with va_arg,
turns it into characters, writes them, and returns how many it wrote so the running count stays
correct. That count — the return value — is easy to forget and the thing testers check hardest.
ft_printf("x=%d done%%", 5) character by character on paper: write exactly what gets printed, and compute the final return count. Then do ft_printf("%s", NULL) — what should print, and what count?4 · Output, and counting
Underneath, printing is the write syscall (write one or more bytes to file descriptor 1, stdout).
You'll wrap it in tiny helpers — write_char, write_str — that each return the number of
characters they wrote, so every path adds to count. The number conversions (int → digits, int →
hex) are the same "peel off digits" logic you'd write for any base: build the characters, then write
them. Reuse what you can from your libft (ft_putstr, ft_putnbr, and friends) — that's exactly
why libft comes first.
%s given NULL (print "(null)"), %p (print 0x then the address
in hex), %x/%X (lower vs upper case, and 0 prints as 0 not empty), and INT_MIN for %d
(you can't just negate it — handle it carefully). Get the return count right in every case. Reason
about each edge on paper before coding it; that's how you avoid the classic bugs.The Project
Reimplement printf. Write a variadic function ft_printf that mimics the real printf for the required conversions, returns the number of characters printed, and is delivered as a library (with a Makefile) exactly to 42's spec.
- 01
Prototype:
int ft_printf(const char *format, ...); - 02
Handle the conversions:
%c %s %p %d %i %u %x %X %%— matching the real printf's output for each. - 03
Return the total number of characters printed (correct in every case, including errors and edge cases).
- 04
Deliver it as a library: a
Makefilewith rulesall,clean,fclean,re, andlibftprintf.a— do NOT do the relink. - 05
Handle the nasty cases:
%swith NULL,%p,%x/%X, andINT_MINfor%d/%i.
- •
Follow the Norm. No memory leaks. No forbidden functions (allowed:
malloc,free,write, and theva_*macros). - •
You may (and should) reuse your libft — this project is why it exists.
- •
Do not implement buffer management or the full printf feature set — only what the subject requires.
ft_printf.c (+ helpers), libftprintf.a, MakefileThis subject taught the two hard parts directly: variadic functions with stdarg (section 2) and walking + dispatching the format string (section 3), plus output and counting (section 4). The conversions themselves are base-conversion and string logic from C From the Metal and your libft. Nothing here is a leap — if a conversion stumps you, it's a small function, reasoned from the sections above.