[HN Gopher] Generalizing Printf in C
       ___________________________________________________________________
        
       Generalizing Printf in C
        
       Author : oliverkwebb
       Score  : 21 points
       Date   : 2025-11-23 23:54 UTC (4 days ago)
        
 (HTM) web link (webb.is-a.dev)
 (TXT) w3m dump (webb.is-a.dev)
        
       | theamk wrote:
       | 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.
        
         | pizlonator wrote:
         | 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
        
         | nwellnhof wrote:
         | 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.
        
       | kazinator wrote:
       | 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.                 char
       | big_enuf_buf[BIG_ENUF_BUF_SIZE];       sprintf(big_enuf_buf,
       | "%x-%04x-%04x", MAJOR, MINOR, BUILD); // preprocessor constants
       | 
       | 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.
       | 
       | The 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";
        
       | kevin_thibedeau wrote:
       | idx should be a size_t.
        
         | einpoklum wrote:
         | 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:
         | 
         | https://stackoverflow.com/q/45740276/1593077
        
       | einpoklum wrote:
       | 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 :                 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);
       | 
       | 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:
       | 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;
        
       | jmclnx wrote:
       | 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.
        
       ___________________________________________________________________
       (page generated 2025-11-28 23:00 UTC)