I often wish I had a simple way to construct a string with sprintf/snprintf without the hassle of defining a local array, like so:
char str[256];
snprintf(str, sizeof(str), format, ...);
use_string(str);
It occurred to me that I could use a compound literal to do this:
static inline char* snprintf_assert(char* str, int max_size, const char* format, ...) {
va_list arg;
va_start(arg, format);
int count = vsnprintf(str, max_size, format, arg);
va_end(arg);
assert(count >= 0 && count < max_size);
return str;
}
// Helper sprintf that allocates a buffer local to the block.
// It's using the fact that (char[N]){} will allocate a l-value
// with lifetime tied to the local block.
#define local_sprintf(N, ...) snprintf_assert((char[N]){}, N, __VA_ARGS__)
Usage:
use_string(local_sprintf(256, format, ...));
I believe this is well defined as the lifetime of the compund literal array will be tied to the enclosing block. Are there any reasons to avoid this?
Here are some disadvantages for this technique over simply defining a local array before the call to
snprintf_assert:compound literals are less portable: defining the array with a fixed size
N(eg:256) is portable to C++ and implementations of C that do not support VLAs nor compound literals, defining the array at the beginning of a block being of course fully portable.this technique is less efficient: the compound literal
(char[N]){}, which you could write(char[N]){0}for backward portability to C99, defines but also initializes the temporary unnamed array, hence you incur the extramemset()ofNbytes to0. This may not be a significant overhead, unless you makeNlarge to accommodate potentially large constructed strings.less readable: this technique is not a common idiom and the casual reader of your code will ponder about the lifetime of the pointer returned by
local_sprintf, despite the explicit name.Conversely, here are some definite advantages:
versatility: simple function call syntax usable in any expression.
simplicity: no need to name the temporary char array.
could be used multiple times via a locally defined pointer with the same scope and lifetime.
yet for this use case the advantage over the classic code seems minuscule:
Too bad the array length still needs to be estimated. Of course the actual array size could be computed and passed via an extra call to
snprintfin the macro, and another one to pass it to snprintf_assert, but multiple evaluation of the macro arguments should be avoided and evaluatingsnprintf3 times seems inelegant to say the least.