4 ms·
A person smarter than me once told me: “Just use snprintf()”. The tiny performance hit is usually worth the safety and simplicity. Only one error condition to
by q3k 4y ago
A person smarter than me once told me: “Just use snprintf()”.
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().
- unwind 4y agoI came here to post exactly that; I think using snprintf() is the proper solution to the "I need to build a string out of many parts in C" problem pretty much every time. 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);
- mtlmtlmtlmtl 4y agoEven better, if you don't mind using the heap, there's also asprintf() which figures out the right length and allocates a buffer itself, then returns it to you. Downside is you have to free the pointer returned of course. And you might want to be careful passing it user input without taking a look at the length first. But you have to do that anyway if you use the stack.
- tom_ 4y agoHere's something I've found a useful upgrade to asprintf, as it frees the passed-in buffer after expanding the format string. You can just pass the same char ** repeatedly (and also pass it as one of the string arguments corresponding to a %s!) and it'll update the pointer appropriately each time. Makes many kinds of string manipulation very simple. 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!
- pajko 4y agoExcept snprintf() does not terminate the string if there's no room for the final \0. So technically this should be snprintf(buf, sizeof buf - 1, "%s%s", "foo", "bar"); buf[sizeof buf - 1] = '\0';
- q3k 4y agoCheck the return value! Unless you really want truncated values, but I would assume most people don't.
- unwind 4y agoI don't think that is correct, my understanding is that it always terminates when possible. FreeBSD's man page [1] has a pretty clear description: 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. [1]: https://man.freebsd.org/cgi/man.cgi?query=snprintf https://man.freebsd.org/cgi/man.cgi?query=snprintf [2]: https://ideone.com/CdLfNS https://ideone.com/CdLfNS
- deleted 4y ago[deleted]
- BananaaRepublik 4y agoBy the way, I realized that sprintf can be used to concatenate by using the buffer within the formatting itself, while snprintf prevents it. Is it a safety issue too? 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"
- ArsenArsen 4y agokeep in mind that you can't do that, here's a quote that explains it well: 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
- attractivechaos 4y agoAt least in glibc, the printf family is much slower than strcpy. It is a huge performance penalty if string copy is frequent (e.g. when you incrementally write to a large string buffer).
- krylon 4y agoAs a bonus, if you pass NULL for the first argument, its return value tells you how much memory it would have used, so you can allocate a buffer that's the right size. Bigger performance hit, but in practice, I found the impact negligible in a codebase that doesn't do a lot of stringy stuff but needs to emit reasonable messages to logfiles etc.
- Someone 4y ago> The tiny performance hit is usually worth the safety and simplicity 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;