[comp.lang.c] space allocation for sprintf

vook@narnia.rtp.dg.com (Eric R Vook) (06/06/91)

Harbison & Steele p.328 contains the following parenthetical comment
after a discussion of sprintf():

"(It is the programmer's responsibility to ensure that the destination
  string area is large enough to contain the output generated by the
  formatting operation.)"

	      ["C: A Reference Manual," 2nd ed, Harbison & Steele, p.328]

I have just a short question, two-part question:
    How do you know how much space to allocate?
Or
    How do you know when you didn't allocate enough?

My application has a varargs function which needs to eventually get to a
single string to pass to routines which do NOT take a varargs interface.

-Eric

---------------------------------------------------------------------
Eric R Vook                  |  Face reality as it is,
Data General Corporation     |    not as it was
vook@dg-rtp.dg.com           |  or as you wish it were.  (Jack Welch)

torek@elf.ee.lbl.gov (Chris Torek) (06/06/91)

In article <1991Jun5.174543.266@dg-rtp.dg.com> vook@narnia.rtp.dg.com
(Eric R Vook) writes:
>How do you know how much space to allocate [for sprintf]?
>How do you know when you didn't allocate enough?

A very good question indeed---by which I mean there are no good answers.

The new BSD stdio has snprintf and vsnprintf functions, which take a
buffer length, and guarantee not to overrun that buffer; they both
return the number of characters they would have printed, were the
buffer infinitely long, and thus:

	char small[40], *p = small;
	needed = snprintf(small, sizeof small, "%s%s%s", a1, a2, a3);
	if (needed >= sizeof small) {
		/* the string was truncated */
		p = malloc(needed + 1);
		if (p == NULL) die("malloc failed");
		(void) snprintf(p, needed + 1, "%s%s%s", a1, a2, a3);
	}

Since many uses of sprintf() are simply to transform the arguments into
a set of characters so that those characters can be displayed somewhere
(e.g., a window), the new BSD stdio also provides `funopen', through
which you can open your own analogue to the `write' function.  This
allows you to pass arbitrarily long items through a short buffer.

Of course, none of these are remotely portable.

I have an approximation for a portable `vsnprintf', included below.  It
depends on vfprintf and on `byte' files (hence will not work with fancy
record files such as found on VMS).  It uses open-but-unlinked files
(easily changed, provided you have atexit()).

/*
 * The function we want is called `vsnprintf': sprintf,
 * with output limited to at most some number of bytes.
 * This exists in the `function stdio' library already.
 * Here we roll our own.
 *
 * MAJOR BOGOSITY:  System V stdio has no way to limit the length
 * to an sprintf.  Instead, we use vfprintf to a temp file which we
 * keep open but unlinked.
 */


#include <stdio.h>
#include <varargs.h>

static FILE *
setfil()
{
	register FILE *f;
	char buf[100];

	(void) sprintf(buf, "/tmp/vsnprintf.%d", getpid());
	if ((f = fopen(buf, "w+")) == NULL) {
		perror(buf);
		(void) fprintf(stderr, "cannot set up vsnprintf, help\n");
		exit(1);
	}
	(void) unlink(buf);
	return (f);
}

int
vsnprintf(str, len, fmt, ap)
	char *str;
	size_t len;
	char *fmt;
	va_list ap;
{
	register int nch;
	static FILE *fil;

	if (len == 0)
		return (0);
	if (fil == NULL)
		fil = setfil();
	(void) fseek(fil, 0L, 0);
	nch = vfprintf(fil, fmt, ap);
	(void) fseek(fil, 0L, 0);
	len--;
	if (nch < len)
		len = nch;
	nch = fread(str, 1, len, fil);
	str[nch] = '\0';
	return (nch);
}
-- 
In-Real-Life: Chris Torek, Lawrence Berkeley Lab CSE/EE (+1 415 486 5427)
Berkeley, CA		Domain:	torek@ee.lbl.gov

henry@zoo.toronto.edu (Henry Spencer) (06/06/91)

In article <1991Jun5.174543.266@dg-rtp.dg.com> vook@narnia.rtp.dg.com (Eric R Vook) writes:
>"(It is the programmer's responsibility to ensure that the destination
>  string area is large enough to contain the output generated by the
>  formatting operation.)"
>
>    How do you know how much space to allocate?
>    How do you know when you didn't allocate enough?

The fast answers are "you have to know how sprintf is being used and figure
out the longest string your parameters can result in" and "basically, you
don't".  A length-limited sprintf was repeatedly proposed to X3J11, and
might have made it into ANSI C if there had been some experience with it.
-- 
"We're thinking about upgrading from    | Henry Spencer @ U of Toronto Zoology
SunOS 4.1.1 to SunOS 3.5."              |  henry@zoo.toronto.edu  utzoo!henry

martin@mwtech.UUCP (Martin Weitzel) (06/07/91)

In article <1991Jun5.174543.266@dg-rtp.dg.com> vook@narnia.rtp.dg.com (Eric R Vook) writes:
>"(It is the programmer's responsibility to ensure that the destination
>  string area is large enough to contain the output generated by the
>  formatting operation.)"
>
>    How do you know how much space to allocate?
>    How do you know when you didn't allocate enough?

In article <1991Jun6.162723.27307@zoo.toronto.edu> henry@zoo.toronto.edu (Henry Spencer) answers:
>The fast answers are "you have to know how sprintf is being used and figure
>out the longest string your parameters can result in" and "basically, you
>don't".

This is also my standard answer and I generally avoid using sprintf
if I can't reliably calculate the required size of the buffer.

But since I know that there are some outthere who will continue to use
sprintf also when the buffer size couldn't be reliably determined, I have
a suggestion (which may also help to check your calculations if you
*think* you made the buffer large enough). If you are too lazy to follow
Chris Toreks complete solution to the problem, at least write your code
as follows:

********* NOTE THAT THIS DOESN'T GUARANTEE TO CATCH ALL PROBLEMS  *********
********* ON ANY MACHINE WHEN WRITING PAST THE END OF THE BUFFER! *********

	#define MAX 40		/* if you think that 40 is enough */
	char buffer[MAX+1];
	buffer[MAX] = '\0';
	sprintf(buffer, ......);
	if (buffer[MAX] != '\0') abort();
	
********* NOTE THAT THIS DOESN'T GUARANTEE TO CATCH ALL PROBLEMS  *********
********* ON ANY MACHINE WHEN WRITING PAST THE END OF THE BUFFER! *********

But in general it's better than nothing, as on many architectures you
will first overwrite other local variables and possibly the stack frame,
then parameters of the function which contains this code and then locals,
stack frames, and parameters of the calling functions. If your function
never returns nor uses any other locals you have a good chance to at least
detect the problem immediately.

********* NOTE THAT THIS DOESN'T GUARANTEE TO CATCH ALL PROBLEMS  *********
********* ON ANY MACHINE WHEN WRITING PAST THE END OF THE BUFFER! *********
-- 
Martin Weitzel, email: martin@mwtech.UUCP, voice: 49-(0)6151-6 56 83

wirzeniu@klaava.Helsinki.FI (Lars Wirzenius) (06/09/91)

In article <1167@mwtech.UUCP> martin@mwtech.UUCP (Martin Weitzel)
writes:
>[ as a suggestion for handling problems when sprintf overflows the buffer ]
>	#define MAX 40		/* if you think that 40 is enough */
>	char buffer[MAX+1];
>	buffer[MAX] = '\0';
>	sprintf(buffer, ......);
>	if (buffer[MAX] != '\0') abort();

May I further suggest that another character than '\0' be used.  A
frequent problem (for me, at least) is the 'off-by-one error' (e.g.,
some limit or array bound is one too small). This type of error would
print one character too many to the buffer, but this isn't noticed if
the validation character is '\0'. If available, a char value that
doesn't correspond to a normal character would be best.
-- 
Lars Wirzenius     wirzeniu@cc.helsinki.fi

peic@core.north.de (Peter Eichler) (06/11/91)

In article <1991Jun5.174543.266@dg-rtp.dg.com>, Eric R Vook writes:

>
>Harbison & Steele p.328 contains the following parenthetical comment
>after a discussion of sprintf():
>"(It is the programmer's responsibility to ensure that the destination
>  string area is large enough to contain the output generated by the
>  formatting operation.)"
>        ["C: A Reference Manual," 2nd ed, Harbison & Steele, p.328]
>
>I have just a short question, two-part question:
>    How do you know how much space to allocate?

Well, look at the stuff you like to put into your destination string.
If I'm printing 5000 floats, it should be more 100 Bytes... ;-)

>Or
>    How do you know when you didn't allocate enough?
If your machine crashes instantly, behaves strangely or your system
crashes later, you WILL know (well maybe, case the error is often hard
to find)

> [...]
>-Eric

Cheerio,
Peter

Since pleasure is the Unique, to reveal Pleasure is itself a unique duty. FGTH
------------------------------------------------------------------------------
SNAIL:Peter Eichler \ Hegelstrasse 3 \ 2800 Bremen 1 \ Germany      Amiga 3000
EMAIL:peic@core.north.de  OR  peic@skuld.north.de:      VOICE:(+)49 421 530642