On 8/5/24, Paul Eggert wrote:
If snprintf (buf, sizeof buf, ...) returns an integer I such that (0 <=
I < sizeof buf) then the snprintf worked, the contents of buf[0] through
buf[I] are valid, and buf[I] == '\0'. Otherwise snprintf didn't work and
buf's contents are indeterminate.
In practice, then "... contents are indeterminate" is too pessimistic.
Nearly always the contents are a good approximation to what is desired.
Often it is necessary to make progress despite imperfect snprintf.
Over several decades of porting, maintaining, and developing apps,
I used these three techniques to make progress with a large handful
of different implementations (and specifications!) of snprintf:
1) Terminate the buffer yourself, anyway:
snprintf(buffer, length, format, args...);
buffer[-1 + length] = '\0';
This defends against implementations that forget to terminate
the buffer on overflow.
2) If the resulting length is needed, then apply strlen() anyway:
len1 = snprintf(buffer, length, format, args...);
buffer[ -1 + length] = '\0';
len2 = strlen(buffer);
This "extra work" will be fast enough because the buffer
will be in the cache. You can be fooled by printing a NUL
byte ('\0') using a "%c" format specifier. But in 2 of my 3
actual cases, this helped discover a bug in the caller:
the argument should not have been a NUL byte.
You also can be fooled by a bug in formatting an extreme
floating point value (+/- infinity, NaN, denormal).
3) Use two canary bytes:
buffer[-1 + length] = '\xA5'; /* any non-NUL byte */
buffer[-2 + length] = '\0';
len1 = snprintf(buffer, length, format, args...);
if ('\0' != buffer[-2 + length]) {
/* Check both canaries and len1 carefully. */
/* Details omitted here. */
}
buffer[-1 + length] = '\0'; /* Force terminate. */
--