int snprintf(char * restrict s, size_t n, const char * restrict format, ...);
snprintf()
nicely prevents overrunning the destination s
. Yet when the destination is insufficient for the complete result, how to detect that and other errors?
Is the following sufficient?.
char buf[11 + 10 + 1];
if (snprintf(buf, sizeof buf, "Random int %d", rand()) >= sizeof buf) {
fprintf(stderr, "Buffer too small"); // Maybe `int` was 64-bit?
exit (EXIT_FAILURE);
}
Snprintf is more secure and if the string number overruns the characters, the string is protected in the buffer even if the format is different. It works with n characters and nth location and hence the location of null character is not considered at all. Allocation of null character memory is preserved in sprintf.
It is safe as you long as you provide the correct length for the buffer.
The snprintf function returns an integer value that equals, in magnitude, the number of characters written to the area addressed by dest . If the value returned is negative, then either the maxlen character limit was reached or some other error, such as an invalid format specification, has occurred.
The snprintf function is similar to sprintf , except that the size argument specifies the maximum number of characters to produce. The trailing null character is counted towards this limit, so you should allocate at least size characters for the string s .
This is part of Can I answer my own question?. Additional answers are welcome.
How to detect
snprintf
errors in C?
Short answer
Recall snprintf()
returns an int
.
Use the wider of size_t
or unsigned
cast.
if ((size_t) snprintf(... ) >= sizeof buf) {
error();
}
or pedantically
int length_needed = snprintf(... );
if (length_needed < 0 || (unsigned) length_needed >= sizeof buf) {
error();
}
Testing for truncation
Sometimes it is very important to detect a truncated string from snprintf()
. Lacking a test can lead to trouble:
char buf[13];
char *command = "format_drive";
char *sub_command = "cancel";
snprintf(buf, sizeof buf, "%s %s", command, sub_command);
system(buf); // system("format_drive") leads to bye-bye data
OK code
A single test if the return value meets or exceeds the size of the destination array is nearly sufficient.
char buf[20];
if (snprintf(buf, sizeof buf, "Random int %d", rand()) >= sizeof buf) {
fprintf(stderr, "Buffer too small");
exit(EXIT_FAILURE);
}
Negative value
The
snprintf
function returns the number of characters that would have been written hadn
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 thann
. C11dr §7.21.6.5 3
Robust code would directly check for a negative value for the rare encoding error. if (some_int <= some_size_t)
unfortunately is not sufficient as the int
will be converted to a size_t
. An int
negative return value then becomes a large positive size_t
. This usually is far larger than the size of the array yet is not specified to be so.
// Pedantic check for negative values
int length_needed = snprintf(... as above ...);
if (length_needed < 0 || length_needed >= sizeof buf) {
fprintf(stderr, "Buffer too small (or encoding error)");
exit(EXIT_FAILURE);
}
Mis-match sign-ness
Some compiler warnings whine about comparing integers of different sign-ness such as gcc's -Wsign-compare
with int
and size_t
. Casting to size_t
seems reasonable.
warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
// Quiet different sign-ness warnings
int length_needed = snprintf(... as above ...);
if (length_needed < 0 || (size_t) length_needed >= sizeof buf) {
fprintf(stderr, "Buffer too small (or encoding error)");
exit(EXIT_FAILURE);
}
Pedantic
C does not specify that the positive values of int
are a sub-range of size_t
. size_t
could be unsigned short
and then SIZE_MAX < INT_MAX
. (I know of no such implementation.) Thus a cast to (size_t) some_int
could alter the value. Instead, casting the positive return value to unsigned
(INT_MAX <= UINT_MAX
is always true) will not alter the value and will ensure the compare is done with the widest unsigned type between unsigned
and size_t
.
// Quiet different sign-ness warnings
int length_needed = snprintf(... as above ...);
if (length_needed < 0 || (unsigned) length_needed >= sizeof buf) {
fprintf(stderr, "Buffer too small (or encoding error)");
exit(EXIT_FAILURE);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With