|For novel ideas about building embedded systems (both hardware and firmware), join the 40,000+ engineers who subscribe to The Embedded Muse, a free biweekly newsletter. The Muse has no hype and no vendor PR. Click here to subscribe.
By Jack Ganssle
Published on embedded.com February, 2014
As I have written before, everyone knows about C's assert macro. But I have found that few embedded people make much use of it. Everyone also knows about snprintf(), yet it, too, is somewhat elusive. Do you use sprintf() or snprintf()?
Most of the embedded code I read makes liberal use of the former, yet it can be a dangerous construct. It's all too easy to overrun the string being written. The snprintf() function makes the developer specify the max length of the string. Yet in casual surveys over the last year, nearly none of the embedded folk I asked knew of the existence of snprintf().
The C11 standard is 700 mind-numbing pages long. All of the many C books I have are replete with examples and advice, and none approach the standard's length. Clearly they cherry pick the best parts of the language. None covers snprintf(), though I do believe this function was added in C99 and some of those books, like the second edition of K&R, predate that version of the language.
The snprintf() function will discard all characters longer than the specified length, so is an especially attractive option when working with variable-sized elements. It can eliminate big classes of buffer overrun bugs.
The function improves maintainability. While the original developer may know with great certainty that sprintf() cannot overrun the string, later changes by others may result in the buffer's length being shortened or the output being increased in size.
Due to NDAs I can't say much about most of the code I read. Let's look at some publically-available software.
Freescale's MQX RTOS, which is sprinkled with more than a little coding horror, does make use of snprintf(), though it is a function that is provided by MQX. sprintf()is much more common, though, and a lot of those functions are called with the %s format specifier which might be trouble (though no doubt the designers were scrupulous in their analysis).
What about Linux? The 3.12.6 release uses snprintf() about 6000 times, compared to 8500 for sprintf(). A superficial reading of some of this shows that in many cases it's obvious the sprintf() is perfectly safe; that's much less apparent for others. No doubt great care was taken to insure buffer overruns are impossible in every circumstance.
Though it might be tempting to use snprintf() to append text to a string, a statement of the form:
snprintf(s, "stuff to append", s);
... will produce an undefined result. Don't do it.
I often put a wrapper around snprintf() to check if the string was truncated to fit in the buffer; if it was an error is invoked (that could be an assertion or other run-time exception handler). This is an example of proactive debugging: writing code that catches bugs before a symptom appears.
Great composers expose themselves to lots of great music. Great writers read a lot of great books. And great programmers should read a lot of code to learn about the language, ways to structure the software, and the like. When you come across a construct that might be unfamiliar, like snprintf(), dig deeper.