Especially since these functions' return values make it hard to use them to step forward in the buffer, so subsequent calls need to walk over the already-inserted characters again and again. That kind of redundant work makes me grit my teeth, which I guess is a sign that I'm a C programmer at heart. :)
Compare:
char buf[1024];
snprintf(buf, sizeof buf, "%s%s", "foo", "bar");
and char buf[1024];
strcpy(buf, "foo");
strlcat(buf, "bar", sizeof buf);
The latter is given a pointer to the base buffer, so it has to walk past the existing characters in order to find the end where the concatenation should start.I also find the "size" arguments confusing and weird. I think it's idiomatic in C to (tightly) associate a size with a buffer pointer, and speaking in terms of "there are n bytes available at this location" makes sense to me. So the prototype really should have been
size_t strlcat_unwind(char *buf, size_t buf_size, const char *src); int xasprintf(char**p,const char *fmt,..) {
int n=0;
char *p2=nullptr;
if(fmt) {
va_list v;
va_start(v);
n=asprintf(&p2,fmt,v);
va_end(v);
}
if(n>=0) {
free(*p);
*p=p2;
}
return n;
}
If you thought asprintf was inefficient, this is worse. But it's very convenient!The snprintf() and vsnprintf() functions will write at most size-1 of the characters printed into the output string (the size'th character then gets the terminating `\0'); if the return value is greater than or equal to the size argument, the string was too short and some of the printed characters were discarded. The output is always null-terminated, unless size is 0.
On a more anecdotal front, I did a quick test on Ideone [2] that seems to behave exactly how I would expect it to (and contradicts your description).
In short: always call snprintf() with the exact size of the buffer. No Obi-Wans.
strcpy(buf, "foo");
sprintf(buf, "%s%s", buf, "bar"); //buf contains "foobar"
strcpy(buf, "foo");
snprintf(buf, sizeof buf, "%s%s", buf, "bar"); //buf contains "bar"Some programs imprudently rely on code such as the following
sprintf(buf, "%s some further text", buf);
to append text to buf. However, the standards explicitly note that the results
are undefined if source and destination buffers overlap when calling sprintf(),
snprintf(), vsprintf(), and vsnprintf(). Depend‐ ing on the version of gcc(1)
used, and the compiler options employed, calls such as the above will not
produce the expected results.The glibc implementation of the functions snprintf() and vsnprintf() conforms to the C99 standard, that is, behaves as described above, since glibc 2.1. Until glibc 2.0.6, they would return -1 when the output was truncated.
- printf(3), Linux man-pages 6.04, 2023-04-01
It need not even be a performance hit. The compiler could recognize the idiom of code such as
snprintf(buffer, bufLen, "%s%s%s", s, t, u);
and optimize it into (example possibly buggy; my C is rusty) b = buffer;
e = b + bufLen:
while((b < e) && (*b++ = *s++));
while((b < e) && (*b++ = *t++));
while((b < e) && (*b++ = *u++));
*b = 0;
The tiny performance hit is usually worth the safety and simplicity. Only one error condition to check, covering both concatenation and copies, sensible 'size' argument behaviour (size includes null terminating byte, null terminating byte is always written)...
Just use snprintf().