chux
chux

Reputation: 154315

How to detect `snprintf` errors?

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

Answers (1)

chux
chux

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

Upvotes: 18

Related Questions