109

Is snprintf always null terminating the destination buffer?

In other words, is this sufficient:

char dst[10];

snprintf(dst, sizeof (dst), "blah %s", somestr);

or do you have to do like this, if somestr is long enough?

char dst[10];

somestr[sizeof (dst) - 1] = '\0';
snprintf(dst, sizeof (dst) - 1, "blah %s", somestr);

I am interested both in what the standard says and what some popular libc might do which is not standard behavior.

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
Prof. Falken
  • 24,226
  • 19
  • 100
  • 173

5 Answers5

90

As the other answers establish: It should:

snprintf ... Writes the results to a character string buffer. (...) will be terminated with a null character, unless buf_size is zero.

So all you have to take care is that you don't pass an zero-size buffer to it, because (obviously) it cannot write a zero to "nowhere".


However, beware that Microsoft's library does not have a function called snprintf but instead historically only had a function called _snprintf (note leading underscore) which does not append a terminating null. Here's the docs (VS 2012, ~~ VS 2013):

http://msdn.microsoft.com/en-us/library/2ts7cx93%28v=vs.110%29.aspx

Return Value

Let len be the length of the formatted data string (not including the terminating null). len and count are in bytes for _snprintf, wide characters for _snwprintf.

  • If len < count, then len characters are stored in buffer, a null-terminator is appended, and len is returned.

  • If len = count, then len characters are stored in buffer, no null-terminator is appended, and len is returned.

  • If len > count, then count characters are stored in buffer, no null-terminator is appended, and a negative value is returned.

(...)

Visual Studio 2015 (VC14) apparently introduced the conforming snprintf function, but the legacy one with the leading underscore and the non null-terminating behavior is still there:

The snprintf function truncates the output when len is greater than or equal to count, by placing a null-terminator at buffer[count-1]. (...)

For all functions other than snprintf, if len = count, len characters are stored in buffer, no null-terminator is appended, (...)

Martin Ba
  • 37,187
  • 33
  • 183
  • 337
  • 30
    What in the name of Aslan were the Microsoft engineers thinking when they introduced `_snprintf` which quietly **removes a key safety feature of `snprintf`** and permits the string to not be null-terminated?! – Colin D Bennett Oct 06 '14 at 15:50
  • 2
    @ColinDBennett - it is weird and mighty annoying and I have no clue if anyone thought at all :-) – Martin Ba Oct 06 '14 at 18:07
  • What's weird is that the documentation lies (or just not up-to-date). It does append a null-terminator. – sekmet64 Nov 14 '14 at 17:59
  • in both the second and the third case if the buffer is large enough it writes the null after the count characters otherwise throws a runtime error. – sekmet64 Nov 14 '14 at 18:07
  • @sekmet64 - your statement does not make sense. The *only* info about the buffer the function has is the `count`, so case 2+3 mean the buffer is too *small* (as far as the function is concerned). -- Although there is a C++ templateized version of this function (5th in the linked docs) - maybe that one has some additional features. – Martin Ba Nov 14 '14 at 19:14
  • @sekmet64 - and indeed: "In C++, these functions have template overloads that invoke the **newer, secure counterparts** of these functions." – Martin Ba Nov 14 '14 at 19:15
  • 2
    @MartinBa yeah sorry, what I tested was `template int _snprintf_s(char (&buffer)[size], size_t count, const char *format [, argument] ...);` and I should also mention that this happens only with /GS (Security Check) compile flag. That function knows size, count and length. – sekmet64 Nov 17 '14 at 14:53
  • 4
    Beware that mingw64 used (uses?) the microsoft _snprintf implementation as "normal" snprintf unless specified otherwise https://nvd.nist.gov/vuln/detail/CVE-2018-1000101 – domenukk Dec 19 '18 at 18:48
  • 1
    At first I read @ColinDBennett's (motivated) rant as: "What in the name of Asian", me being a non native English-speaker wondered what kind of idiom that was, and why Asia has anything to do with Microsoft :D. Still don't get the expression and what Narnias Lion has to do with anything. Enlighten me! :D – Sajjon Apr 15 '20 at 12:10
  • 3
    @Sajjon It's an admittedly silly (and perhaps totally original) exclamation of exasperation ( https://idioms.thefreedictionary.com/in+the+name+of+God ) perhaps slightly as a minced oath ( https://en.wikipedia.org/wiki/Minced_oath ). Another example might be "What in the name of Zeus...?!" ( https://forum.wordreference.com/threads/in-the-name-of-zeus.2132965/ ) – Colin D Bennett Apr 15 '20 at 16:09
  • @ColinDBennett: It matches strncpy. I can see a use for both functions. – Joshua Sep 08 '20 at 15:22
  • 1
    Microsoft's _snprintf() predates any standard for snprintf(), I'm pretty sure, and therefore wasn't removing a safety feature, merely not implementing one. Now, I'm sure there were (pre-standard) implementations of snprintf() that did always promise to nul-terminate (the character 0 is nul not null) and MSFT clearly didn't copy best-in-class . – Swiss Frank Nov 14 '22 at 13:29
23

According to snprintf(3) manpage.

The functions snprintf() and vsnprintf() write at most size bytes (including the trailing null byte ('\0')) to str.

So, yes, no need to terminate if size >= 1.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
piotr
  • 5,657
  • 1
  • 35
  • 60
  • 5
    And thank god for that; this is the only sensible design. The whole point of the checked versions of these functions is to be *safe*, and it'd be terrible if you had to do all the termination malarkey by hand. – Kerrek SB Oct 09 '11 at 22:31
  • 2
    I would recommend testing it out on the platform(s) you're using before relying on this. Even if it _should_ write the null byte, I know I've run into implementations that didn't (it might have been with MinGW, which used an older MS runtime). – Dmitri Oct 10 '11 at 00:33
  • 2
    I don't believe the phrase you cite actually means what you think it means. As a native English speaker it clearly means that as long as the string, plus its nul-terminator, is at or under size, it will all be written. But this sentence by itself is absolutely not describing what is written should size not be enough to write all. – Swiss Frank Nov 14 '22 at 13:31
15

According to the C standard, unless the buffer size is 0, vsnprintf() and snprintf() null terminates its output.

The snprintf() function shall be equivalent to sprintf(), with the addition of the n argument which states the size of the buffer referred to by s. If n is zero, nothing shall be written and s may be a null pointer. Otherwise, output bytes beyond the n-1st shall be discarded instead of being written to the array, and a null byte is written at the end of the bytes actually written into the array.

So, if you need to know how big a buffer to allocate, use a size of zero, and you can then use a null pointer as the destination. Note that I linked to the POSIX pages, but these explicitly say that there is not intended to be any divergence between Standard C and POSIX where they cover the same ground:

The functionality described on this reference page is aligned with the ISO C standard. Any conflict between the requirements described here and the ISO C standard is unintentional. This volume of POSIX.1-2008 defers to the ISO C standard.

Be wary of the Microsoft version of vsnprintf(). It definitely behaves differently from the standard C version when there is not enough space in the buffer (it returns -1 where the standard function returns the required length). It is not entirely clear that the Microsoft version null terminates its output under error conditions, whereas the standard C version does.

However, note that Microsoft has changed the rules (vsnprintf()) since this answer was originally written:

Beginning with the UCRT in Visual Studio 2015 and Windows 10, vsnprintf is no longer identical to _vsnprintf. The vsnprintf function conforms to the C99 standard; _vnsprintf is kept for backward compatibility with older Visual Studio code.

Similar comments apply to snprintf() and sprintf().

Note also the answers to Do you use the TR 24731 safe functions? (see MSDN for the Microsoft version of the vsprintf_s()) and the Mac solution for the safe alternatives to unsafe C standard library functions?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
5

Some older versions of SunOS did weird things with snprintf and might have not NUL-terminated the output and had return values that didn't match what everyone else was doing, but anything that has been released in the past 10 years have been doing what C99 says.

Art
  • 19,807
  • 1
  • 34
  • 60
3

The ambiguity starts from the C Standard itself. Both C99 and C11 have identical description of snprintf function. Here is the description from C99:

7.19.6.5 The snprintf function
Synopsis
1 #include <stdio.h> int snprintf(char * restrict s, size_t n, const char * restrict format, ...);
Description
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 negative 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.

On the one hand the sentence

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

says that
if (the s points to a 3-character-long array, and) n is 3, then 2 characters will be written, and the characters beyond the 2nd one are discarded; then the null character is written after those 2 (and the null character will be the 3rd character written).

And this I believe answers the original question.
THE ANSWER:
If copying takes place between objects that overlap, the behavior is undefined.
If n is 0 then nothing is written to the output
otherwise, if no encoding errors encountered, the output is ALWAYS null-terminated (regardless of whether the output fits in the output array or not; if not then some characters are discarded such that the output array is never overflown),
otherwise (if encoding errors are encountered) the output can stay non-null-terminated.

On the other hand
The last sentence

Thus, the null-terminated output has been completely written if and only if the returned value is nonnegative and less than n

gives ambiguity (or my English is not good enough). I can interpret this sentence in at least two ways:
1. The output is null-terminated if and only if the returned value is nonnegative and less than n (which means that if the returned value is not less than n, i.e. the output (including the terminating null character) does not fit in the array, then the output is not null-terminated).
2. The output is complete (no characters have been discarded) if and only if the returned value is nonnegative and less than n.


I believe that the interpretation 1 above contradicts THE ANSWER, causes misunderstanding and lengthy discussions. That is why the last sentence describing the snprintf function needs a change in order to remove any ambiguity (which gives grounds for writing a Proposal to the C language Standard).
The example of non-ambiguous wording I believe can be taken from http://en.cppreference.com/w/c/io/fprintf (see 4)), thanks to @"Martin Ba" for the link.

See also the question "snprintf: Are there any C Standard Proposals/plans to change the description of this func?".

Robin Kuzmin
  • 742
  • 4
  • 12
  • 5
    Your interpretation 1 does not seem at all plausible to me. I parse that sentence as "The output (which is, incidentally, null-terminated) has been completely written if ..." which I can only understand as #2. – zwol May 25 '18 at 04:27
  • 1
    The negation of the sentence "null-terminated output has been completely written" is "null-terminated output has *not* been completely written". Nothing more. The negated sentence by itself does not imply that *anything* has been written (this includes incomplete null-terminated output, incomplete non-null-terminated output, or colourless green ideas). Some other place in the standard says what exactly is written when output is incomplete, and *that place* states that the output is null terminated unless it's empty (n == 0). – n. m. could be an AI May 25 '18 at 09:17