Re: better snprintf replacement, anyone?

der Mouse (mouse@RODENTS.MONTREAL.QC.CA)
Tue, 22 Jul 1997 21:27:31 -0400

>> The point beyond which a system is so legacy it's not worth even
>> trying to support it, for me, includes systems without calls like
>> snprintf.
> Sun would probably not be totally pleased with having Solaris
> 2.5.1(*) referred to as a legacy OS.

I really don't give a damn what Sun is or isn't pleased with. Any
vendor that's too sluggish to have implemented such rudimentary
security measures as implementing functions like snprintf that border
on essential for writing reasonably secure code deserves no sympathy.
Hell, even back when I was working on 4.3 I'd hacked in fopenstr(),
which opened a string buffer (including length) and returned a FILE *
to which I/O could be done, which amounts to much the same thing. It
took me all of maybe half an hour. Even with all the inefficiency and
overhead associated with being a big software house, I have trouble
seeing it taking more than one person-day. There even is a complete
stdio package - including snprintf - available free, even to people
like commercial software vendors; they have no excuse. None.

>> Let me give you an example of what you suggest:

>> if (strlen(a) + 1 + 10 + 3 + 1 + 10 + 4*sizeof(long) + 1 + strlen(b) +
>> 1 + strlen(c) + 2 >= sizeof buf);
>> sprintf(buf, "%s %d see %d %lx %s %s\n", a, i, j, p, b, c);

> As a comment to support Theo's point of view, it's worth noting that
> the above example includes about 6 errors. [...]

It may or may not, depending on how much the programmer knew about the
values the (presumably int) variables printed with %d could take on,
and how machine-dependent the code is. Specifically, it assumes

- that i and j occupy no more than nine digits each when printed with %d
- that the sizeof() unit occupies no more than 4 characters when
printed as part of %lx

In addition, the way the arithmetic is done implies that the format
string is quite different from what it actually is; I had to look
carefully to get the figure "nine" in the above.

> #define N_DEC_DIGIT(type) ((((sizeof(type) * 8 - 1) * 30103 + 99999) \
> / 100000) + 1)

8? You should be using CHAR_BIT. I normally use the equivalent of
#define N_DEC_DIGIT(type) (((CHAR_BIT*sizeof(type))/3)+2)
on the theory that it requires no more digits in decimal than in octal,
plus a sign, plus no more than one digit lost to divison round-down.
(Overestimating is less serious than underestimating; since most values
will be much less than the maximum, it's an overestimate anyway.)

> (For those puzzled about N_DEC_DIGIT: log10(2) ~= .30103 and the sign
> bit doesn't count).

You're assuming there is a sign bit. (Signed) int may be represented
in other ways, such as a negative-base representation, or maybe even
something like BCD. (Note also that log(2) < .30103, so your #define
will underestimate the number of digits. Fortunately (for you :-), the
rounding-up-to-an-integer part salvages the situation unless
sizeof(type) is quite implausibly huge.)

Actually, the "smaller than the maximum" note above is a reason to use
snprintf rather than estimating. When I write something like

snprintf(buf,"%d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
i, j, tbl[i], tbl[j], tbl[tbl[i]+tbl[j]],
....
n, count_interesting_entries(tbl,n));

most of the numbers will usually be one or two digits, maybe three or
four, not eight or nine. If I'm mallocking a buffer, this is not
particularly serious, but if I'm doing "goto bail" or moral equivalent
on apparent overflow, it is.

der Mouse

mouse@rodents.montreal.qc.ca
7D C8 61 52 5D E7 2D 39 4E F1 31 3E E8 B3 27 4B