On GNU systems, if you want to generalize printf, all you need is vfprintf - because there is:
"fmemopen(3)" that creates FILE* that writes to pre-allocate dbuffer
"open_memstream(3)" that creates FILE* that writes to auto-allocated buffer;
and if that's not sufficient, there is "fopencookie(3)" which takes general callbacks and creates FILE* that redirects all operations to those callbacks.
If that does not work for some reason, then having custom callback with user-passed 3 parameters is too much. Why add dedicated FILE* or "size" parameters which are only ever used in one specific case? Do a generic "void * context" argument ("int (write)(char data, void * context)" + "void * context") and let user figure out how to use it.
Yeah
Pretty sure a vfprintf-like function sits at the bottom of the printf stack in all of the libc's I've surveyed (which includes BSDs). And yeah, BSDs also support memstream APIs, for example https://man.openbsd.org/fmemopen.3
fmemopen and open_memstream are both part of POSIX, so they're not restricted to GNU systems and can be used portably. fopencookie is a GNU extension, though.
sprintf can be safely used.
- For some conversions, you can establish an upper bound on how many characters they will produce. E.g. a positive decimal integer not more than 9999 does not consume more than four characters.
- It's possible to specify truncation. e.g. "%.64s" prints at most 64 characters from the string argument.
- There are enirely static cases that can be worked out at compile time, e.g.
Even if the buffer isn't big enough, and the behavior is formally undefined, it is entirely analyzable at compile time and we have support for that: the compiler can work out that the conversion needs, e.g., 13 bytes, including null termination, but the buffer only has 12.char big_enuf_buf[BIG_ENUF_BUF_SIZE]; sprintf(big_enuf_buf, "%x-%04x-%04x", MAJOR, MINOR, BUILD); // preprocessor constantsThe reasons for analyzing to it wouldn't necessarily just be for diagnostics, but possibly for compiling it down to a literal:
char big_enuf_buf[BIG_ENUF_BUF_SIZE] = "A1-0013-000A";idx should be a size_t.
Actually, there are historical reasons why `int` may be used. Look at the definition of the %n format specifier - it expects an `int *` argument. And all of the famirly functions return `int`'s ... see also:
And in the old days, there was disp_printf() from Zortech. That was a very nice printf. You supplied the row and column to allow printing anywhere on the terminal.
A popular standalone printf-family library in the embedded world is, well, printf :
https://github.com/eyalroz/printf
which is independent of a C standard library (it doesn't actually do any I/O itself). Originally by Marco Paland, now maintained, or 'curated' by myself (so, this is a bit of a self-plug, even though I can barely claim authorship). It offers this generalization :
The library is not performance-oriented, but rather small-code-size-oriented. The family of functions therefore all have a single backing implementation. You might think that implementation must use the function generalization quoted above, but actually it uses a gadget with some more functionality:int fctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, ...); int vfctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char* format, va_list arg);typedef struct { void (*function)(char c, void* extra_arg); void* extra_function_arg; char* buffer; printf_size_t pos; printf_size_t max_chars; } output_gadget_t;