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 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. 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);
}