Skip to main content

Overview

The Minitalk project uses a custom implementation of printf called ft_printf, along with supporting utility functions. This custom implementation is part of the “libft” library and provides formatted output capabilities without relying on the standard library’s printf.

Source Code Location

  • Header: libft/ft_printf.h - Lines 1-32
  • Implementation: libft/ft_printf.c - Lines 1-96

Why a Custom Printf?

Educational Purpose

Understanding how formatted output works at a low level by implementing it from scratch.

Project Requirements

42 School projects often require students to use only their own libraries (libft) rather than standard C library functions.

Minimal Dependencies

Reduces external dependencies to only the most basic system calls like write() and unistd.h.

Fine-Grained Control

Complete control over output behavior and format specifier handling.

Header File: ft_printf.h

Include Dependencies

#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
stdio.h
header
Standard input/output definitions (though ft_printf bypasses most standard functions).
unistd.h
header
UNIX standard functions, primarily for the write() system call.
stdarg.h
header
Variable argument list macros (va_list, va_start, va_arg, va_end).
stdlib.h
header
Standard library definitions for general utilities.

Function Declarations

int	ft_printf(char const *str, ...);
int	ft_write_str(char *str);
int	ft_write_num(char *base, long int num);
int	ft_write_pointer(char *base, unsigned long num);
int	ft_write_char(char c);

Primary Function

ft_printf
int (char const *str, ...)
Main formatted output function. Similar to standard printf, accepts a format string and variable arguments.Parameters:
  • str: Format string with format specifiers (e.g., "%d", "%s")
  • ...: Variable number of arguments matching the format specifiers
Returns: Total number of characters written

Utility Functions

ft_write_str
int (char *str)
Writes a string to standard output.Parameters:
  • str: String to write
Returns: Number of characters written
ft_write_num
int (char *base, long int num)
Writes a number in a specified base (decimal, hexadecimal, etc.).Parameters:
  • base: String representing the digit characters for the base (e.g., DEC_DIG, HEX_LOW)
  • num: The number to write
Returns: Number of characters written
ft_write_pointer
int (char *base, unsigned long num)
Writes a pointer address in hexadecimal format.Parameters:
  • base: Base character set (typically HEX_LOW)
  • num: Pointer address as unsigned long
Returns: Number of characters written (excluding “0x” prefix)
ft_write_char
int (char c)
Writes a single character to standard output.Parameters:
  • c: Character to write
Returns: Number of characters written (always 1)

Format Base Constants

# define DEC_DIG "0123456789"
# define HEX_UP "0123456789ABCDEF"
# define HEX_LOW "0123456789abcdef"
Lines: 27-29 These constants define the character sets used for different numeric bases:
DEC_DIG
#define
Decimal digits - Used for %d, %i, and %u format specifiers.Base 10: characters 0-9
HEX_UP
#define
Uppercase hexadecimal - Used for %X format specifier.Base 16: characters 0-9, A-F
HEX_LOW
#define
Lowercase hexadecimal - Used for %x and %p format specifiers.Base 16: characters 0-9, a-f
The base string represents the digits available in a numbering system. When converting a number:
// Converting 255 to hexadecimal using HEX_LOW
// 255 ÷ 16 = 15 remainder 15
// First digit: HEX_LOW[15] = 'f'
// Second digit: HEX_LOW[15] = 'f'
// Result: "ff"

// Converting 42 to decimal using DEC_DIG
// 42 ÷ 10 = 4 remainder 2
// First digit: DEC_DIG[4] = '4'
// Second digit: DEC_DIG[2] = '2'
// Result: "42"
The ft_write_num function uses modulo and division operations with the base length to extract digits and index into the base string.

Implementation: ft_printf.c

Format Specifier Handler

int	ft_format_specifiers(char *str, va_list args)
{
	int		i;

	i = 0;
	if (str[i] == 'd' || str[i] == 'i')
		return (ft_write_num(DEC_DIG, va_arg(args, int)));
	else if (str[i] == 'x')
		return (ft_write_num(HEX_LOW, va_arg(args, unsigned int)));
	else if (str[i] == 'X')
		return (ft_write_num(HEX_UP, va_arg(args, unsigned int)));
	else if (str[i] == 'c')
		return (ft_write_char(va_arg(args, int)));
	else if (str[i] == 'u')
		return (ft_write_num(DEC_DIG, va_arg(args, unsigned int)));
	else if (str[i] == 's')
		return (ft_write_str(va_arg(args, char *)));
	else if (str[i] == 'p')
	{
		write (1, "0x", 2);
		return (ft_write_pointer(HEX_LOW, (unsigned long)va_arg(args, void *)));
	}
	else if (str[i] == '%')
		return (write(1, "%", 1));
	return (0);
}
Lines: 15-40 This function handles all format specifiers by delegating to appropriate utility functions.

Supported Format Specifiers

if (str[i] == 'd' || str[i] == 'i')
    return (ft_write_num(DEC_DIG, va_arg(args, int)));
Type: int (signed)Example:
ft_printf("Number: %d", -42);  // Output: "Number: -42"
ft_printf("PID: %i", 12345);   // Output: "PID: 12345"
else if (str[i] == 'u')
    return (ft_write_num(DEC_DIG, va_arg(args, unsigned int)));
Type: unsigned intExample:
ft_printf("Value: %u", 4294967295U);  // Output: "Value: 4294967295"
else if (str[i] == 'x')
    return (ft_write_num(HEX_LOW, va_arg(args, unsigned int)));
Type: unsigned intExample:
ft_printf("Hex: %x", 255);  // Output: "Hex: ff"
ft_printf("Hex: %x", 3466); // Output: "Hex: d8a"
else if (str[i] == 'X')
    return (ft_write_num(HEX_UP, va_arg(args, unsigned int)));
Type: unsigned intExample:
ft_printf("Hex: %X", 255);  // Output: "Hex: FF"
ft_printf("Hex: %X", 3466); // Output: "Hex: D8A"
else if (str[i] == 'c')
    return (ft_write_char(va_arg(args, int)));
Type: int (promoted from char)Example:
ft_printf("Char: %c", 'A');  // Output: "Char: A"
ft_printf("%c", 65);         // Output: "A" (ASCII 65)
else if (str[i] == 's')
    return (ft_write_str(va_arg(args, char *)));
Type: char*Example:
ft_printf("Message: %s", "Hello");  // Output: "Message: Hello"
else if (str[i] == 'p')
{
    write (1, "0x", 2);
    return (ft_write_pointer(HEX_LOW, (unsigned long)va_arg(args, void *)));
}
Type: void*Example:
int x = 42;
ft_printf("Address: %p", &x);  // Output: "Address: 0x7ffd5c3a4b8c"
The “0x” prefix is written separately and returns 2 characters, which is added to the total count.
else if (str[i] == '%')
    return (write(1, "%", 1));
Example:
ft_printf("100%% complete");  // Output: "100% complete"

Main Printf Function

int	ft_printf(char const *str, ...)
{
	va_list	args;
	int		i;
	int		total;

	i = 0;
	total = 0;
	va_start(args, str);
	while (str[i] != '\0')
	{
		if (str[i] == '%')
		{
			i++;
			total += ft_format_specifiers((char *)str + i, args);
		}
		else
			total += write(1, &str[i], 1);
		i++;
	}
	va_end(args);
	return (total);
}
Lines: 42-64

Implementation Details

1

Initialize Variable Arguments

va_list args;
va_start(args, str);
Sets up the variable argument list. va_start initializes args to point to the first unnamed argument after str.
2

Iterate Through Format String

while (str[i] != '\0')
{
    // Process each character
}
Walks through the format string character by character until the null terminator.
3

Handle Format Specifiers

if (str[i] == '%')
{
    i++;
    total += ft_format_specifiers((char *)str + i, args);
}
When a % is encountered:
  • Increment i to point to the format character
  • Call ft_format_specifiers with the address of that character
  • Add the return value (characters written) to total
4

Write Regular Characters

else
    total += write(1, &str[i], 1);
Non-format characters are written directly using the write() system call.write() signature:
ssize_t write(int fd, const void *buf, size_t count);
  • fd = 1: Standard output
  • buf = &str[i]: Address of the current character
  • count = 1: Write one byte
5

Cleanup and Return

va_end(args);
return (total);
Cleans up the variable argument list and returns the total number of characters written.

Return Value

total
int
The cumulative count of all characters written to standard output, including:
  • Regular characters from the format string
  • Characters from format specifier outputs
  • The “0x” prefix for pointer format
This matches the behavior of standard printf.

Usage in Minitalk

Server Usage

// server.c:36
ft_printf("SUCCESS!, Server is ready :D! The PID: %d\n", getpid());

// server.c:37
ft_printf("Waiting fot the string...\n");

// server.c:28 (in handle_sigusr)
ft_printf("%c", c);

Startup Message

Displays PID using %d specifier

Status Message

Simple string output

Character Output

Prints received characters using %c

Client Usage

// client.c:76 - Usage message
return (ft_printf("Usage : ./client <PID> <string to send>\n"), 1);

// client.c:81 - Error message
ft_printf("Wrong PID\n");

// client.c:88 - Error message
ft_printf("Client failed sending signal\n");

Usage Instructions

Help message for incorrect invocation

Validation Error

PID format validation failure

Runtime Error

Signal transmission failure

Key Design Features

write(1, &str[i], 1);
Uses write() directly instead of higher-level buffered I/O functions like putchar() or fwrite(). This provides:
  • Immediate output (no buffering delays)
  • Fine-grained control over output
  • Minimal overhead
va_list args;
va_start(args, str);
va_arg(args, int);  // Extract next argument
va_end(args);
The <stdarg.h> macros enable handling variable numbers of arguments:
  • va_list: Type to hold argument information
  • va_start: Initialize argument list
  • va_arg: Retrieve next argument (requires type)
  • va_end: Cleanup
total += write(1, &str[i], 1);
total += ft_format_specifiers((char *)str + i, args);
Every output operation returns the number of characters written, which is accumulated in total. This mimics standard printf behavior and can be useful for tracking output size.
The implementation separates concerns:
  • ft_printf: Main entry point, format string parsing
  • ft_format_specifiers: Format specifier routing
  • ft_write_*: Specialized output functions
This makes the code easier to understand, test, and maintain.

Comparison with Standard Printf

Similarities

  • Variable argument handling
  • Format specifier syntax (%d, %s, etc.)
  • Returns character count
  • Supports common format types

Differences

  • No width/precision modifiers (e.g., %5d, %.2f)
  • No floating-point support (%f, %e, %g)
  • No field width or padding
  • Simpler implementation
The custom ft_printf is intentionally simplified for educational purposes and project requirements, focusing on the most commonly used format specifiers.

Function Call Graph

Build docs developers (and LLMs) love