Printf reimplementation in C using dispatch tables with function pointers for clean format specifier handling.
- π Project overview
- π Concept guide
- π§ Struct array implementation
- βοΈ Utility functions
- π‘ Potential improvements
- π Notes
- π οΈ Compilation and usage
- βοΈ License
- Objective: Implement a function,
ft_printf, that recreates the behavior of the standard C library'sprintffunction. The function must handle variadic arguments and format specifiers to produce formatted output. - Function signature:
int ft_printf(const char *format, ...);whereformatis the format string containing text and format specifiers. - Authorized functions:
malloc,free,write,va_start,va_arg,va_copy,va_end. - Returns:
- βοΈ The number of characters printed to stdout.
- β Negative value if an error occurs during writing.
- Supported specifiers:
%c,%s,%p,%d,%i,%u,%x,%X,%%.
- Master variadic functions and understand how
stdarg.hworks under the hood. - Implement efficient format string parsing and argument type handling.
- Develop clean, maintainable code architecture using function pointers and dispatch tables.
- The function must handle the conversion specifiers:
c,s,p,d,i,u,x,X,%. - The function should mimic the behavior of the real
printffunction as closely as possible. - No need to implement buffer management; direct
writesystem calls are acceptable. - Return value must be the number of characters printed, or -1 on error.
cc -Wall -Wextra -Werror ft_printf.c ft_arg_utils.c ft_base_utils.c.- Handle NULL pointers gracefully (print
(null)for strings,(nil)for pointers). - Undefined behavior for invalid format specifiers is acceptable but should be handled consistently.
- Variadic functions: Handle variable numbers of arguments using
stdarg.hmacros (va_start,va_arg,va_end). - Dispatch tables: Use function pointers instead of if/else chains for cleaner, more maintainable code.
- Format string parsing: Iterate through format strings, distinguishing literal characters from
%specifiers. - Type promotion: Handle automatic promotion of
char/shorttointin variadic functions. - Base conversion: Convert numbers between decimal and hexadecimal representations.
- Memory safety: Robust handling of NULL pointers and edge cases.
- Direct output: Write directly to file descriptors without internal buffering.
This project uses a dispatch table approach with function pointers instead of traditional if/else chains. This provides cleaner, more maintainable code and better performance through direct function pointer lookup.
typedef struct s_format
{
char selector;
int (*f)(va_list *args, const char selector);
} t_format;The function parses the format string character by character. When a % is encountered, it looks up the next character in the dispatch table and calls the corresponding handler function.
int ft_printf(const char *format, ...)
{
va_list args;
int ret;
const t_format handlers[9] = {
{SEL_CHAR, &ft_sel_strs}, {SEL_STR, &ft_sel_strs},
{SEL_PTR, &ft_sel_ptr},
{SEL_DEC, &ft_sel_nums}, {SEL_INT, &ft_sel_nums}, {SEL_UINT, &ft_sel_nums},
{SEL_HEX, &ft_sel_hexs}, {SEL_HEXUP, &ft_sel_hexs},
{SEL_PERCENT, &ft_sel_percent}
};
ret = 0;
va_start(args, format);
while (*format)
{
if (*format == SEL_PERCENT)
{
format++;
ret += ft_handle_specifier(*format, &args, handlers);
}
else
ret += write(FD, format, 1);
format++;
}
va_end(args);
return (ret);
}The ft_handle_specifier function iterates through the dispatch table to find the matching format specifier:
static int ft_handle_specifier(const char format_char, va_list *args,
const t_format *handlers)
{
int ret;
ret = 0;
if (!format_char)
return (-1);
while (handlers->selector && handlers->selector != format_char)
handlers++;
if (handlers->selector == format_char)
ret += handlers->f(args, handlers->selector);
else
{
if (format_char != SEL_PERCENT)
ret += write(FD, "%", 1);
ret += write(FD, &format_char, 1);
}
return (ret);
}The project is organized into modular utility files for better code organization and maintainability:
| Function | File | Format specifiers | Description |
|---|---|---|---|
ft_sel_strs |
ft_arg_utils.c |
%c, %s |
Handles character and string output. Promotes chars to unsigned char per stdarg.h specs |
ft_sel_ptr |
ft_arg_utils.c |
%p |
Handles pointer addresses. Converts to hexadecimal with "0x" prefix or prints "(nil)" for NULL |
ft_sel_nums |
ft_arg_utils.c |
%d, %i, %u |
Handles signed and unsigned decimal integers with appropriate type casting |
ft_sel_hexs |
ft_arg_utils.c |
%x, %X |
Handles hexadecimal conversion (lowercase and uppercase) |
ft_sel_percent |
ft_arg_utils.c |
%% |
Handles literal percent sign output |
| Function | File | Purpose |
|---|---|---|
ft_putnbr_base |
ft_base_utils.c |
Converts numbers to specified base representation |
ft_putunbr_base |
ft_base_utils.c |
Converts unsigned numbers to specified base representation |
ft_strlen |
ft_base_utils.c |
Calculates string length for internal operations |
| Function | File | Purpose |
|---|---|---|
ft_printf |
ft_printf.c |
Main entry point. Parses format string and manages variadic arguments |
ft_handle_specifier |
ft_printf.c |
Dispatch table lookup and handler function invocation |
-
Extended format specifier support: The current struct array implementation could be extended to support additional format specifiers like
%f,%e,%gfor floating-point numbers, or field width and precision modifiers. -
Buffered output optimization: While the current implementation uses direct
writesystem calls for simplicity, implementing an internal buffer could reduce the number of system calls and improve performance for large outputs. -
Enhanced error reporting: The current implementation returns -1 for write errors, but could be enhanced to provide more detailed error information, such as distinguishing between different types of failures (invalid file descriptor, disk full, etc.).
- Struct array over if/else chains: The dispatch table approach provides cleaner code organization, better maintainability, and easier extension compared to traditional cascading if/else statements.
- Modular handler functions: Each format type (strings, numbers, hexadecimal, etc.) is handled by specialized functions, promoting code reusability and easier debugging.
- Consistent error handling: All handler functions follow the same pattern for error detection and return value management.
- Function pointer overhead: While minimal, function pointer calls have slight overhead compared to direct function calls, but this is offset by the improved code organization and maintainability.
- Function length: Each function stays within the 25-line limit through careful modularization.
- Variable declarations: All variables are declared at the beginning of their scope as required by the 42 Norm.
- Function parameters: No function exceeds the 4-parameter limit, with complex data passed via structures where needed.
A comprehensive test file (main.c) is provided that compares the implementation against the standard printf function with extensive test cases.
# Compile the library
make
# Compile with the provided test main
cc -Wall -Wextra -Werror ft_printf.c ft_arg_utils.c ft_base_utils.c main.c# Run the comprehensive test suite
./a.outThe test suite validates:
- Format specifiers: Characters, strings, pointers, decimals, integers, unsigned, hexadecimal, and percent signs
- Edge cases: NULL strings/pointers, multibyte characters, negative values
- Boundary conditions: Maximum values, recursive pointers, long strings
- Return value validation: Compares character count with standard
printf - Color-coded output: Green for matching results, red for discrepancies
Additional test cases are available as commented sections for extended testing.
This project is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. You're free to study, modify, and share this code for educational purposes, but commercial use is prohibited.