Characters written so far in snprintf
Asked Answered
I

2

7

Lately, I noticed a strange case I would like to verify:

By SUS, for %n in a format string, the respective int will be set to the-amount-of-bytes-written-to-the-output. Additionally, for snprintf(dest, 3, "abcd"), dest will point to "ab\0". Why? Because no more than n (n = 3) bytes are to be written to the output (the dest buffer).

I deduced that for the code:

int written;
char dest[3];
snprintf(dest, 3, "abcde%n", &written);

written will be set to 2 (null termination excluded from count). But from a test I made using GCC 4.8.1, written was set to 5. Was I misinterpreting the standard? Is it a bug? Is it undefined behaviour?

Edit:

@wildplasser said:

... the behavior of %n in the format string could be undefined or implementation defined ...

And

... the implementation has to simulate processing the complete format string (including the %n) ...

@par said:

written is 5 because that's how many characters would be written at the point the %n is encountered. This is correct behavior. snprintf only copies up to size characters minus the trailing null ...

And:

Another way to look at this is that the %n wouldn't have even been encountered if it only processed up to 2 characters, so it's conceivable to expect written to have an invalid value...

And:

... the whole string is processed via printf() rules, then the max-length is applied ...

Can it be verified be the standard, a standard-draft or some official source?

Indissoluble answered 28/8, 2014 at 18:58 Comment(2)
Looks like a bug (in the library implementation) to me.Kho
I expect you should first look at the returnvalue from snprintf(). The "side effects" are different; the behavior of %n in the format string could be undefined or implementation defined if the returnvalue is >= the 2nd argument. In any case, the implementation has to simulate processing the complete format string (including the %n ) to obtain the correct return value, so the side-effect can be obtained on the fly.Frow
D
7

written is 5 because that's how many characters would be written at the point the %n is encountered. This is correct behavior. snprintf only copies up to size characters minus the trailing null (so in your case 3-1 == 2. You have to separate the string formatting behavior from the only-write-so-many characters.

Another way to look at this is that the %n wouldn't have even been encountered if it only processed up to 2 characters, so it's conceivable to expect written to have an invalid value. That's where there would be a bug, if you were expecting something valid in written at the point %n was encountered (and there wasn't).

So remember, the whole string is processed via printf() rules, then the max-length is applied.

Dehydrate answered 28/8, 2014 at 19:12 Comment(7)
Hmmm, the text of the Standard contains "... number of characters written to the output stream so far ..."; 'c', 'd', and 'e' were not written to the output streamKho
@Kho - The characters were written to the output stream, insofar as the output stream is a data buffer "behind the scenes" in the printf() function. Then the snprintf behavior of only copying up to size characters from the "output stream" occurs. See Heinz's answer for a reference to ISOC99.Dehydrate
then again, maybe the text referring to "%n" does not apply to sprintf() because this function does not write to output streams.Kho
Think of how you'd implement snprintf(): you'd call vasprintf(), then memcpy(), then free(). Here you can clearly see the output stream is the buffer allocated by vasprintf(), but it happens behind the scenes.Dehydrate
@Dehydrate maybe you would do that... implementors who care about stack overflows wouldn'tRorry
How might you overflow the stack, given that vasprintf() allocates its memory on the heap?Dehydrate
Joking aside, I do realize calling asprintf() would be inefficient and bloated. You'd actually want to write circularly to a small scratch buffer on the stack, but my point is you still have to write the whole string. snprintf() by definition returns the length of the whole string that would have been written if the size argument was unlimited.Dehydrate
B
5

It is not a bug: ISOC99 says

The snprintf function is equivalent to fprintf [...] output characters beyond the n-1st are discarded rather than being written to the array [...]

So it just discards trailing output but otherwise behaves the same.

Bridewell answered 28/8, 2014 at 19:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.