[Effective-cpp] Item 2: The String Formatters of Manor Farm, Part 1: sprintf

Balog Pal pasa at lib.hu
Thu Oct 28 18:52:01 EDT 2004


> There is no length safety. There is no way to explicitly limit how much
> of the character array is used. This can and often does result in buffer
> overrun errors.

No length safety is a prblem everyone shall be aware. Unfortunaltely sprintf
has no way to pass in the buffer length, and stop there.  Many
implementations provide such safe versions (snprintf ot alike) , those are
not standard, but I'd suggest use those instead of sprintf.
The only way to fight the length problem with sprintf is to use large enough
buffer. That is easy to guess if the format specifier use width-limit for
the elements, otherwise it is unbound for %s strings, and can trick you with
floating.

There are tamed versions, like MFC's CString::Format().  It uses the same
format string and argument row, just the result goes to a handled memory
block.  The library calculates the needed length and it never overflows.

Well, unless it does -- the library coming with MSVC guessed the lengh
needed for a double as 128.  while in float.h we have
#define DBL_MAX         1.7976931348623158e+308 /* max value */

The bug got fixed in V6.0, and Im not aware of further bugs -- so writing a
safe length alculator is after all possible, but it may worth a peek at the
implementation, how easy that is.

> There is no type safety. Errors in the format specification are not
> caught at compile time. They're caught at run time, if you're lucky.
> Lint tools will catch many of these problems.

This point is very narrow that way.  sprintf is a ... taking function.  In
C++ such functions are rare, and I observed the programmers are growing
unaware of its nature.

... will:

- take anything without a slightest warning
- some types will suffer promotions/conversion using rules no one knows.
(not me at least)
- it is UNDEFINED BEHAVIOR to pass any non-pod.

Yeah, if you see everyone just passing CString arguments vithout observable
problems does not mean it is legal -- or that some operator gets called. No,
CString happens to contain a single member, that is a poitner to the string
data.  And MSVC passes classes as a plain struct, just dumping the data on
the stack, no ctors, or dtors called anywhere.  Anyone else thinks  it's
overly fragile?

To avoid the ...-related problems I created a set of type-converter
functions and every argument is passed through them.  The functions just do
an implicit cast, the catch is they know the proper target type.

the usage is like this:

sprintf(buf, "num: %d longhex: %lX f: %f str: %s",  f_d(i), f_lX(hex),
f_f(flo), f_s(str)  );

the letters after f_ shall exactly match the letters after % in the format
string.
It actually caught me some bugs at compile time, not many but some, where I
mistyped an argument, and makes me feel much safer.

I enclose the implementation at the end,  note that I made the type-map
using the MSDN documentation not the standard.


One more subtle point not mentioned: it is possible to pass the target as an
argument.  Eg:

char buf[100];
strintf(buf, "res:%d", f_d(res));
...
strintf(buf, "thread:%04X -- %s", f_X(thrId), f_s(buf) );

Most likely by the time the %s is processed buf is overwritten with
"thread:0001 -- " that is not 0-terminated, and we face UB besides getting
the wrong result.

Paul

- ------------

#define MAKE_FORMAT_TEMPL(letter, type) template<class T> inline \
 type f_##letter(const T& t) {return t;}

MAKE_FORMAT_TEMPL(c, int)
MAKE_FORMAT_TEMPL(C, int)
MAKE_FORMAT_TEMPL(d, int)
MAKE_FORMAT_TEMPL(i, int)
MAKE_FORMAT_TEMPL(o, int)
MAKE_FORMAT_TEMPL(u, int)
MAKE_FORMAT_TEMPL(x, int)
MAKE_FORMAT_TEMPL(X, int)
MAKE_FORMAT_TEMPL(e, double)
MAKE_FORMAT_TEMPL(E, double)
MAKE_FORMAT_TEMPL(f, double)
MAKE_FORMAT_TEMPL(g, double)
MAKE_FORMAT_TEMPL(n, int *)
MAKE_FORMAT_TEMPL(p, const void *)
MAKE_FORMAT_TEMPL(s, const char *)
MAKE_FORMAT_TEMPL(S, const wchar_t *)

MAKE_FORMAT_TEMPL(ld, long int)
MAKE_FORMAT_TEMPL(li, long int)
MAKE_FORMAT_TEMPL(lo, long int)
MAKE_FORMAT_TEMPL(lx, long int)
MAKE_FORMAT_TEMPL(lX, long int)

MAKE_FORMAT_TEMPL(lu, unsigned long int)

MAKE_FORMAT_TEMPL(hd, short int)
MAKE_FORMAT_TEMPL(hi, short int)
MAKE_FORMAT_TEMPL(ho, short int)
MAKE_FORMAT_TEMPL(hx, short int)
MAKE_FORMAT_TEMPL(hX, short int)

MAKE_FORMAT_TEMPL(hu, unsigned short int)

#ifdef _MSC_VER
MAKE_FORMAT_TEMPL(I64d, __int64 )
MAKE_FORMAT_TEMPL(I64i, __int64 )
MAKE_FORMAT_TEMPL(I64o, __int64 )
MAKE_FORMAT_TEMPL(I64x, __int64 )
MAKE_FORMAT_TEMPL(I64X, __int64 )
#endif //_MSC_VER

MAKE_FORMAT_TEMPL(hc, char)
MAKE_FORMAT_TEMPL(hC, char)
MAKE_FORMAT_TEMPL(lc, wchar_t)
MAKE_FORMAT_TEMPL(lC, wchar_t)

MAKE_FORMAT_TEMPL(hs, const char *)
MAKE_FORMAT_TEMPL(hS, const char *)
MAKE_FORMAT_TEMPL(ls, const wchar_t *)
MAKE_FORMAT_TEMPL(lS, const wchar_t *)


#undef MAKE_FORMAT_TEMPL






More information about the Effective-cpp mailing list