Lightweight text formatting in the style of vprintf. Does not require heap or large memory buffers. It is intended for use with gcc in embedded systems. The function prototype is: int format(int(*outchar)(int), const char *fmt, va_list ap); Characters are sent, one-by-one, to the designated outchar() function. If the outchar() function return value is less than 0 then format() returns immediately with -1. Otherwise it returns 0 after all characters have been processed. The following format sequences are supported: %[flags][width]c -- argument is char (promoted to int) %[flags][width][.precision]s -- argument is pointer to char (NULL will be treated as "(null)") %[flags][width][.precision][size]{i|d} -- argument is signed int, printed as decimal with a sign %[flags][width][.precision][size]{u|b|o|x|X} -- argument is unsigned int, printed as decimal, binary, octal or hex Note %f and other floating point formats are not supported. Flags are zero or more of: "-" : output is left-justified within specified width instead of right-justified "0" : right-justified numbers with less digits than precision have leading "0"s instead of spaces "+" : positive signed integers have a leading "+" " " : positive signed integers have a leading space "#" : ignored Width is minimum total number of characters to be output, padded with space or "0" as required. Specify "*" to use the value of the next int argument. Precision is maximum number of string characters to output (including 0), or mininum number of numeric digits to output (possibly with leading "0"s). Specify ".*" to use the the value of the next int argument as the precision. Size is one of: none : argument is an int "l" : argument is a long int "ll" : argument is a long long int All other values must be cast to one of these. Examples: // Write formatted text via putchar().. int example_printf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); int res = format(putchar, fmt, ap); va_end(ap); return res; } // Write formatted text via the (hypothetical) send_via_uart() function, // expanding '\n' to '\r\n'. Fail if if DTR is not asserted. int example_uart_printf(const char *fmt, ...) { int txchar(int c) { if (c == '\n') send_via_uart('\r'); int dtr = send_via_uart(c); // returns 0 if no DTR return dtr ? 0 : -1; } va_list ap; va_start(ap, fmt); int res = format(txchar, fmt, ap); // res == -1 if no DTR va_end(ap); return res; } // Write formatted text to *str, but not more than size bytes including the // zero-termination. Return the total number of characters that would have // been written, even if str was too small to contain it. int example_snprintf(char *str, int size, const char *fmt, ...) { int total = 0; int addchar(int c) { if (total++ < size-1) // add character only if it fits *str++ = c; return 0; // never fails } va_list ap; va_start(ap, fmt); format(addchar, fmt, ap); // write via addchar() va_end(ap); *str = 0; // ensure zero termination return total; // return total formatted chars } Note GCC allows the prototypes for these functions to include: __attribute__((format (printf, x, y))) Where x and y are the argument offsets of *fmt and ... respectively. This enables parameter sanity checking at compile time. The program test.c simply outputs a large number of formatted strings, it can be built to use format() or printf(). The Makefile builds both flavors and verifies they have the same output. This software is released as-is into the public domain, as described at https://unlicense.org. Do whatever you like with it.