Re: better snprintf replacement, anyone?

Sten Gunterberg (sten@ERGON.CH)
Tue, 22 Jul 1997 18:23:31 +0200

On 22nd July, James Bonfield <jkb@MRC-LMB.CAM.AC.UK> wrote:
>
> [...] I wrote a worst-cast scenario function to determine the maximum length
> of output for a sprintf style request. It doesn't handle unicode or anything
> fancy, but does handle most things reasonably well. I'm not saying it's 100%
> foolproof (eg it makes assumptions that we're not on anything bigger than a
> 64bit system), but it's a start.
>

Below follows some code I hacked together to do my own snprintf()
*without* having to parse the format string. The performance freaks
among you will frown on my waste of cycles, I'm sure :)

--Sten

/*
* Variants of snprintf() and vsnprintf() with a definable callback
* handler function to catch buffer overflows.
*
* Use it only if you have the "spare" cycles needed to effectively
* do every snprintf operation twice! Why is that? Because everything
* is first vfprintf()'d to /dev/null to determine the number of bytes.
* Perhaps a bit slow for demanding applications on slow machines,
* no problem for a fast machine with some spare cycles.
*
* You don't have a /dev/null? Every Linux contains one for free!
*
* On my Ultra-1 (143Mhz) this implementation of snprintf() runs
* at 45% of the speed of native sprintf(). This leaves my with
* "just" under 100k snprintf()'s per second to play with :-)
*
* Because the format string is never even looked at, all current and
* possible future printf-conversions should be handled just fine.
*
* Written July 1997 by Sten Gunterberg (gunterberg@ergon.ch)
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

static void
default_handler (const char *func, int max, const char *fmt)
{
int len;

fprintf(stderr,
"*** short buffer in %s() with max=%d and format=%s",
func, max, fmt);
if ((len = strlen(fmt)) > 0 && fmt[len-1] != '\n')
fputc('\n', stderr);
abort();
}

static void (*overflow_handler)() = default_handler;

void
set_nprintf_overflow_handler (void (*arg)(const char *, int, const char *))
{
overflow_handler = arg;
}

static int
needed (const char *fmt, va_list argp)
{
static FILE *sink = NULL;

if (sink == NULL)
{
if ((sink = fopen("/dev/null", "w")) == NULL)
{
/* Hmm. Find a better way to handle this! */
fprintf(stderr, "*** failed to open /dev/null ***\n");
abort();
}
}

return vfprintf(sink, fmt, argp);
}

int
snprintf (char *buf, int max, const char *fmt, ...)
{
va_list argp;
int bytes;

va_start(argp, fmt);
if (needed(fmt, argp) > max)
{
(*overflow_handler)("snprintf", max, fmt);
exit(1); /* should never get here, really */
}
bytes = vsprintf(buf, fmt, argp);
va_end(argp);

return bytes;
}

int
vsnprintf (char *buf, int max, const char *fmt, va_list argp)
{
if (needed(fmt, argp) > max)
{
(*overflow_handler)("vsnprintf", max, fmt);
exit(1); /* should never get here, really */
}

return vsprintf(buf, fmt, argp);
}

#ifdef STANDALONE

int
main (int argc, char **argv)
{
char buf[10];

snprintf(buf, 10, "test %d\n", 123); /* works */
snprintf(buf, 10, "test %d\n", 123456); /* fails */
}

#endif