Pining for toString()
One of the things that I’ve had difficulty with coming from Java to C is dealing with strings. In Java, string handling is just so easy, they’re nice little objects that keep to themselves and don’t bother anybody. Everybody loves them, and wants to be them. Everybody has a method called toString() that lets them turn into one if you’ll just let them. Want to stick an EnterpriseBaseBeanFactoryFactory into a JLabel? EnterpriseBaseBeanFactoryFactory.toString() and you’re set. Wouldn’t it be nice if we had toString() in C?
We sort of do. Like all things in C, it requires a bit of work, but we can replicate the effect. The function you are looking for is snprintf(), which is in stdio.h. snprintf() is a variadic function that works similarly to printf(). However, instead of printing to stdout, it places the string into a passed-in char *. snprintf() takes at least 3 arguments: a destination char *, the maximum number of bytes to output, and a format string. Following the format string, a variable number of extra arguments corresponding to the format string are passed in, similarly to printf().
Snprintf() does not do any pointer allocation or deallocation, so you should create your char * ahead of time. It can be a stack allocated char array too if you only need a temporary variable. The second argument should be less than or equal to the size of the char *. This size argument prevents snprintf() from writing off the end of your allocated memory.
Take the following code:
int value = 1000; char to_string; int return_value = snprintf(to_string, 5, "%d", value); printf("Return value: %d, The String: %s", return_value, to_string);
This code creates a char array sized 5, and converts the number 1000. This code produces the following output:
Return value: 4, The String: 1000
…but 1000, is only 4 digits, right? Let’s see what happens if we reduce to_string’s size to 4, and the size argument to 4:
int value = 1000; char to_string; int return_value = snprintf(to_string, 4, "%d", value); printf("Return value: %d, The String: %s", return_value, to_string);
…produces an output of:
Return value: 4, The String: 100
Snprintf() wrote “100” into the string and returned 4. What happened? When snprintf() tries to write a number of bytes greater than the limit, it returns the number of bytes that it would have written if the size was large enough. You can use this to test for failures and to determine how big your string needs to be. However, this does not explain why the string was truncated. Snprintf() returned 4, and our limit and array size were both 4, it should have worked, right?
The problem is that we are forgetting the terminating . Snprintf() appends to the end of strings it creates, and size needs to be left over for this null. However, the return value does not account for the terminating null.
What About Sprintf()?
In stdio.h, there is another function that appears to do the same thing: sprintf(). Sprintf() doesn’t take a size_t like snprintf() does. Surely this means that sprintf() does some sort of automatic checking right? No, it doesn’t. Sprintf() will happily write off the end of your passed-in char * and cause all sorts of problems. As we all know, this will result in undefined behavior. It might seem to work. It might segfault. It might even cause lions to emerge from your fridge and eat you. One thing I’m sure we can all agree on is: lions are terrifying. Do yourself a favor, and just use snprintf().
Like many things in C, word on the street is that snprintf() behaves differently across different platforms. This blog post was written using GCC and Linux. I can’t promise that snprintf() works exactly the same in Visual Studio, or on some random mainframe or whatever. As always, try it before you buy it.