Reputation: 154315
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);
}
Upvotes: 9
Views: 5684
Reputation: 154315
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
to indicate the length of the string that would have been written or a negative value for an encoding error.
if ((size_t) snprintf(... ) >= sizeof buf) {
error();
}
Pedantically, use the wider of size_t
or unsigned
cast, so better as:
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);
}
Upvotes: 18