How to detect `snprintf` errors?

Viewed 2600
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);
}
1 Answers

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