General Reentrant printf() Function

General Reentrant printf() Function
Philip J. Erdelsky
June 8, 1992
Public domain; no restrictions on use
Any C compiler
printf, editing, formatting, printing
A generalized version of printf() that supports arbitrary output methods and is reentrant, but does not support floating-point editing.

The function printf() and its companions fprintf(), sprintf(), vprintf(), etc., are found in every standard C library and are called by nearly every C application. However, without source code they are rather difficult to adapt to environments other than those for which they were written, and they are not necessarily reentrant.

Our version of these functions, which we call general_printf(), is as portable and versatile as we could make it, and it is completely reentrant. You may have to serialize access to the output device, but you won't have to serialize access to general_printf() itself.

We don't support floating-point editing, but we do support other features of the original printf() and also some add-ons. The editing phrase %b edits an integer in binary, and the phrase %u edits an unsigned integer in decimal. Both permit the standard field parameter and the modifier l if the integer is long. An asterisk, if used as a field or precision parameter, takes on the value of the corresponding argument, which must be an integer. The editing phrase %x uses small letters in the edited result, the editing phrase %X uses capital letters.

We tacitly assume that function arguments of type "short" and "char" are expanded to type "int", and that the size of an argument of type "long" or "char *" is a multiple of the size of an argument of type "int". These assumptions are true for all compilers that we are familiar with.

The call on general_printf() is as follows:

n = general_printf(output_function, output_pointer, control_string, argument_pointer);
void (*output_function)(void *, int);function to be called to output a character of the edited result
void *output_pointer; pointer to be passed to (*output_function)()
char *control_string; control string, also called a format string
int *argument_pointer; pointer to first argument in argument list
int n; number of characters sent to output device, or a negative error code

The function uses the control string to edit the argument list and sends the resulting string of ASCII characters to the output device, one at a time, by calling the function (*output_function)() as follows:

e = (*output_function)(output_pointer, c);
void *output_pointer; implementation-defined pointer
int c; character of edited string
int e; nonnegative value for successful operation, or negative error code

The pointer may be a file pointer, a drive number, or a pointer to some implementation-defined descriptor block. The function general_printf() makes no assumptions about it, but merely passes it on. The function need not return the character c if the operation is successful, but it must return a negative value if there was an error. This value is then returned unchanged by general_printf() as its functional value.

The function general_printf() need not be called directly. You may find it much more convenient to use it as a basis for your own versions of the standard C library functions. Here are examples of possible alternative versions of fprintf() and sprintf() under DOS:

#include <stdio.h>

static int output(void *fp, int c)
return putc(c, (FILE *) fp);

int alternative_fprintf(FILE *fp, char *control, ...)
return general_printf(output, fp, control, (int *)(&control+1));

static int fill_string(void *p, int c)
*(*(char **)p)++ = c;
return 0;

int alternative_sprintf(char *s, char *control, ...)
int n = general_printf(fill_string, &s, control, (int *)(&control+1));
*s = 0;
return n;

The implementation of general_printf() uses "short" arithmetic in a few places where "int" arithmetic might have been used. Of course, if the two types are the same, there is no harm in doing this; but if "short" variables occupy two bytes and "int" variables occupy four bytes, some stack space is saved by using "short" variables. However, there is also a much better reason. If general_printf() is given corrupt input, it may hang up in one of its internal loops. If the loop counter is four bytes long, the system or process may effectively freeze because executing even a tight loop over 2 billion times will take longer than the user is prepared to wait. If the loop counter is only two bytes long, it will finish after at most 32,767 iterations.