← 42 projects
subject · 42 · Projects

ft_printf

Build your own printf — variadic functions, format parsing, and output

8–12 hours

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.

After this subject you can
  • › 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.

Key idea
Two hard parts, and the whole project is really just these: (1) how does one function accept a *variable, unknown* number of arguments (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.
sum.cc
1#include <stdarg.h>
2int sum(int count, ...) { // 'count' tells us how many follow
3 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, advance
8 va_end(ap);
9 return total;
10}
11// sum(3, 10, 20, 30) -> 60
Watch out
va_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.

shape.cc
1int ft_printf(const char *fmt, ...) {
2 va_list ap;
3 va_start(ap, fmt);
4 int count = 0; // characters printed so far
5 for (int i = 0; fmt[i]; i++) {
6 if (fmt[i] == '%') {
7 i++; // move to the conversion char
8 count += handle_conversion(fmt[i], ap); // dispatch on fmt[i]
9 } else {
10 count += write_char(fmt[i]); // ordinary char, print it
11 }
12 }
13 va_end(ap);
14 return count; // total characters written
15}

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.

your turn — do it on paper
Trace 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.

Note
Edge cases the testers love: %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.

Mandatory
  • 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 Makefile with rules all, clean, fclean, re, and libftprintf.a — do NOT do the relink.

  • 05

    Handle the nasty cases: %s with NULL, %p, %x/%X, and INT_MIN for %d/%i.

Constraints
  • •

    Follow the Norm. No memory leaks. No forbidden functions (allowed: malloc, free, write, and the va_* 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.

Deliverable
ft_printf.c (+ helpers), libftprintf.a, Makefile
Build & check
make # builds libftprintf.a
cc -Wall -Wextra -Werror main.c -L. -lftprintf -o test && ./test
You were taught everything for this

This 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.

built the project?