3

A comment on one of my answers has left me a little puzzled. When trying to compute how much memory is needed to concat two strings to a new block of memory, it was said that using snprintf was preferred over strlen, as shown below:

size_t length = snprintf(0, 0, "%s%s", str1, str2);
// preferred over:
size_t length = strlen(str1) + strlen(str2);

Can I get some reasoning behind this? What is the advantage, if any, and would one ever see one result differ from the other?

Community
  • 1
  • 1

7 Answers7

5

I was the one who said it, and I left out the +1 in my comment which was written quickly and carelessly, so let me explain. My point was merely that you should use the pattern of using the same method to compute the length that will eventually be used to fill the string, rather than using two different methods that could potentially differ in subtle ways.

For example, if you had three strings rather than two, and two or more of them overlapped, it would be possible that strlen(str1)+strlen(str2)+strlen(str3)+1 exceeds SIZE_MAX and wraps past zero, resulting in under-allocation and truncation of the output (if snprintf is used) or extremely dangerous memory corruption (if strcpy and strcat are used).

snprintf will return -1 with errno=EOVERFLOW when the resulting string would be longer than INT_MAX, so you're protected. You do need to check the return value before using it though, and add one for the null terminator.

Community
  • 1
  • 1
R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
3

If you only need to determine how big would be the concatenation of the two strings, I don't see any particular reason to prefer snprintf, since the minimum operations to determine the total length of the two strings is what the two strlen calls do. snprintf will almost surely be slower, because it has to check the parameters and parse the format string besides just walking the two strings counting the characters.

... but... it may be an intelligent move to use snprintf if you are in a scenario where you want to concatenate two strings, and have a static, not too big buffer to handle normal cases, but you can fallback to a dynamically allocated buffer in case of big strings, e.g.:

/* static buffer "big enough" for most cases */
char buffer[256];
/* pointer used in the part where work on the string is actually done */
char * outputStr=buffer;
/* try to concatenate, get the length of the resulting string */
int length = snprintf(buffer, sizeof(buffer), "%s%s", str1, str2);
if(length<0)
{
    /* error, panic and death */
}
else if(length>sizeof(buffer)-1)
{
    /* buffer wasn't enough, allocate dynamically */
    outputStr=malloc(length+1);
    if(outputStr==NULL)
    {
        /* allocation error, death and panic */
    }
    if(snprintf(outputStr, length, "%s%s", str1, str2)<0)
    {
        /* error, the world is doomed */
    }
}

/* here do whatever you want with outputStr */

if(outputStr!=buffer)
    free(outputStr);
Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
0

One advantage would be that the input strings are only scanned once (inside the snprintf()) instead of twice for the strlen/strcpy solution.

Actually, on rereading this question and the comment on your previous answer, I don't see what the point is in using sprintf() just to calculate the concatenated string length. If you're actually doing the concatenation, my above paragraph applies.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 4
    @Pete Wilson: You are incorrect. From `man snprintf` on my system: "when snprintf() is called with size=0 ... C99 allows str to be NULL in this case, and gives the return value (as always) as the number of characters that would have been written in case the output string has been large enough." – Greg Hewgill Apr 10 '11 at 22:42
0

EDIT: random, mistaken nonsense removed. Did I say that?

EDIT: Matteo in his comment below is absolutely right and I was absolutely wrong.

From C99:

2 The snprintf function is equivalent to fprintf, except that the output is written into an array (specified by argument s) rather than to a stream. If n is zero, nothing is written, and s may be a null pointer. Otherwise, output characters beyond the n-1st are discarded rather than being written to the array, and a null character is written at the end of the characters actually written into the array. If copying takes place between objects that overlap, the behavior is undefined.

Returns 3 The snprintf function returns the number of characters that would have been written had n been sufficiently large, not counting the terminating null character, or a neg ative value if an encoding error occurred. Thus, the null-terminated output has been completely written if and only if the returned value is nonnegative and less than n.

Thank you, Matteo, and I apologize to the OP.

This is great news because it gives a positive answer to a question I'd asked here only a three weeks ago. I can't explain why I didn't read all of the answers, which gave me what I wanted. Awesome!

Community
  • 1
  • 1
Pete Wilson
  • 8,610
  • 6
  • 39
  • 51
  • 2
    From the `snprintf` man page: "Concerning the return value of `snprintf()`, SUSv2 and C99 contradict each other: when `snprintf()` is called with `size=0` then SUSv2 stipulates an unspecified return value less than 1, while C99 allows `str` to be `NULL` in this case, and gives the return value (as always) as the number of characters that would have been written in case the output string has been large enough." So, in both case there's no core dump, and, as far as your compiler conforms to the C99 standard, that call is well-defined. – Matteo Italia Apr 10 '11 at 22:40
  • And, from the C99 standard (§7.19.6.5 ¶2) "If `n` is zero, nothing is written, and `s` may be a null pointer." (and ¶ 3) "The `snprintf` function returns the number of characters that would have been written had `n` been sufficiently large, not counting the terminating null character" – Matteo Italia Apr 10 '11 at 22:44
  • @M, thanks and now I'll downvote myself on the other question where I didn't read all of the answers :-) (thinks: like hell I will :-) – Pete Wilson Apr 10 '11 at 22:59
0

You need to add 1 to the strlen() example. Remember you need to allocate space for nul terminating byte.

Richard Schneider
  • 34,944
  • 9
  • 57
  • 73
  • Adding 1 to the `snprintf` expression is also needed, as it is not included in the return value. I just omitted it from both for simplicity. –  Apr 10 '11 at 22:45
0

The "advantage" that I can see here is that strlen(NULL) might cause a segmentation fault, while (at least glibc's) snprintf() handles NULL parameters without failing.

Hence, with glibc-snprintf() you don't need to check whether one of the strings is NULL, although length might be slightly larger than needed, because (at least on my system) printf("%s", NULL); prints "(null)" instead of nothing.


I wouldn't recommend using snprintf() instead of strlen() though. It's just not obvious. A much better solution is a wrapper for strlen() which returns 0 when the argument is NULL:

size_t my_strlen(const char *str)
{
    return str ? strlen(str) : 0;
}
Philip
  • 5,795
  • 3
  • 33
  • 68
  • `snprintf` does not accept `NULL` for `%s`-specifier arguments. – R.. GitHub STOP HELPING ICE Apr 11 '11 at 00:46
  • R..: is that once again a stupid GNU extension? Because my glibc-`snprintf()` happily accepts `NULL` arguments for the `%s` specifier. – Philip Apr 11 '11 at 10:16
  • It's UB, so the implementation is entitled to do whatever it wants. glibc acts as if the argument were `"(null)"` in this case. – R.. GitHub STOP HELPING ICE Apr 11 '11 at 14:19
  • R..: thanks for pointing this out. I updated my answer, though I now might delete it as well... – Philip Apr 11 '11 at 14:46
  • Please don't delete, since this discussion is very informative to the issues. Note that your `my_strlen` wrapper would return 0 for `NULL` while glibc's `snprintf` would output 6 bytes for `NULL`. This could lead to truncation of later parts of the string, or if someone were stupid enough to just use `sprintf` and assume the right length had been computed, it would lead to a buffer overflow. – R.. GitHub STOP HELPING ICE Apr 11 '11 at 15:38
0

So snprintf( ) gives me the size a string would have been. That means I can malloc( ) space for that guy. Hugely useful.

I wanted (but did not find until now) this function of snprintf( ) because I format tons of strings for output later; but I wanted not to have to assign static bufs for the outputs because it's hard to predict how long the outputs will be. So I ended up with a lot of 4096-long char arrays :-(

But now -- using this newly-discovered (to me) snprintf( ) char-counting function, I can malloc( ) output bufs AND sleep at night, both.

Thanks again and apologies to the OP and to Matteo.

Pete Wilson
  • 8,610
  • 6
  • 39
  • 51