Search code examples
cc-stringsc99

Reasonable to use a compound literal array as a temporary string buffer?


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?


Solution

  • 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 extra memset() of N bytes to 0. This may not be a significant overhead, unless you make N large 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.

      char *fullname = local_sprintf(256, "%s %s", first, last);
      register_user(fullname);
      output_user(fullname);
      

      yet for this use case the advantage over the classic code seems minuscule:

      char fullname[156];
      snprintf(fullname, sizeof fullname, "%s %s", first, last);
      register_user(fullname);
      output_user(fullname);
      

    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 snprintf in the macro, and another one to pass it to snprintf_assert, but multiple evaluation of the macro arguments should be avoided and evaluating snprintf 3 times seems inelegant to say the least.