Nowadays I mandate the use of snprintf() for simplicity sake. It is known to exist and well documented.
Do your users actually use it correctly? I.e., with the pattern
int len = snprintf(buffer, sizeof buffer, FORMATTING, values...); if (len < 0 || len >= (int)sizeof buffer) { /* Fail! */ }or do they ignore its return value? I bet the latter. The
len == (int)sizeof buffer case in particular leads to an unterminated buffer but with a complete string in it.
Unless you deviate from the snprintf() API, of course.
In most C libraries targeted for microcontrollers, printf() doesn't support printing floating point numbers for example. Are those implementations suddenly useless?
No, but I have seen dozens of Arduino developers being surprised as to why suddenly their sketch is significantly larger, or worse, no longer fit in their MCU.
They just cannot associate it with printf()'ing or snprintf()'ing floating-point numbers, often through some compiled-but-not-actually-used debugging output, for some reason. (snprintf, unlike print, comes from avr-libc or newlibc in Arduino. print comes from the Arduino core Printable class.)
When you want to implement localization using the standard techniques, a printf() that fails to format e.g.
%2$s (second additional parameter as a string) correctly (but happens to print
%1$s correctly if it is the first conversion specifier, because it silently ignores the
n$ part), can be even more aggravating than a printf() that balloons when you use it with floats.
I definitely do not see such a printf() as superior to a well-designed different new interface.
Often, when formatting floating-point numbers in a microcontroller, I am only interested in a specific range of values and at a limited precision. For example, I might want to limit to range -99999999 to +99999999, with at most eight significant digits, with values exceeding the aforementioned range clamped to the limits. (Possible values shown then includes +1234.5678 and -.87654321.)
With a just one additional macro and related formatting struct, I can add to the
emit() interface I described earlier a
LIMITED_FLOAT(value, limits) formatting method, where
limits are a comma-separated list of (optional) structure initializers specifying the various limits, including things like whether to include integral zero for -1 <
value < +1, whether to omit trailing fractional zero digits, and so on. No parsing, as it is converted to a function call with the populated structure as a parameter (with unspecified fields zeros), so this uses minimal RAM and code.
(Formatting such range-limited floating-point values is much simpler/easier/more efficient than general floating-point formatting, especially if the maximum number of decimal digits is also specified. Very little RAM is needed for the conversion, even when doing exact correct rounding. It is the very large values, and nonzero values close to zero, that require more complex code and more RAM, when formatting floating-point numbers into decimal forms.)
Also, on Harvard architecture AVRs (like ATmega32U4 that I happen to like) the C99
emit() interface (or GCC and Clang
_Generic, to be specific) can differentiate between
__flash const char *,
const char *, and
char *, making it trivial to support immutable strings in Flash/ROM. How would you do that with the printf() interface? A new formatting specifier would work, but the compiler won't recognize it, leading to all sorts of hassles (see Linux kernel printk() in my earlier message).
The
emit() interface does the right thing, and will give a compile-time error. It won't silently map a
__flash-qualified type to an otherwise matching non-
__flash-qualified type.