I've done something similar to your vsnprintf()-based code by using snprintf() into an undersized buffer, realloc()ing the buffer to the returned value, then calling snprintf() again. This works well for reused buffers that have a length associated with them and may need to grow over time, but clearly vsnprintf() could do the same thing.
Oh, I wouldn't use asprintf in any performance-critical code. But I wouldn't be using character strings in any performance-critical code either, so that issue doesn't arise for me.
It's a shame they didn't offer msprintf(), which would use malloc(), and asprintf(), which would use alloca(), for cases where you only want a temporary string without heap usage/fragmentation overhead.
You can't use alloca from inside a library function, since it would allocate within the library function's stack frame and the allocation would no longer be valid when the function returned. (Or rather, you can use alloca within library functions, but you can't return a pointer to that allocation, so it wouldn't be useful here.)
Theoretically you could define an alloca()ed-pointer-returning Xsprintf as a macro, though... (but ask tptacek notes, it's probably a bad idea).
Something like this (C99; given p, stack-allocates p_sasprintf_buf of size at most 16 if the string would fit, and uses asprintf otherwise):
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#define SASPRINTF_MAXLEN 16
#define SASPRINTF_MERGE(a, b) a ## b
#define SASPRINTF_LEN(p) SASPRINTF_MERGE(p, _sasprintf_len)
#define SASPRINTF_BUF(p) SASPRINTF_MERGE(p, _sasprintf_buf)
#define SASPRINTF(p, fmt, ...) \
size_t SASPRINTF_LEN(p) = snprintf(NULL, 0, (fmt), __VA_ARGS__); \
char SASPRINTF_BUF(p)[SASPRINTF_LEN(p) <= SASPRINTF_MAXLEN ? SASPRINTF_LEN(p) + 1 : 0]; \
if (SASPRINTF_LEN(p) <= SASPRINTF_MAXLEN) { \
snprintf(SASPRINTF_BUF(p), SASPRINTF_LEN(p) + 1, (fmt), __VA_ARGS__); \
p = SASPRINTF_BUF(p); \
} else { \
if (asprintf(&p, (fmt), __VA_ARGS__) == -1) \
err(1, "SASPRINTF_L at %s, %d", __FILE__, __LINE__); \
}
#define SASPRINTF_FREE(p) do { \
if (p != SASPRINTF_BUF(p)) \
free(p); \
} while(0)
/* Test harness */
int main(void);
int main(void) {
char *p, *p2;
SASPRINTF(p, "%s", "foo");
SASPRINTF(p2, "%s", "Really long string, really.");
printf("%s\n%s\n", p, p2);
SASPRINTF_FREE(p);
SASPRINTF_FREE(p2);
exit(EXIT_SUCCESS);
}
I was going to say "...but you have to be pretty insane to do this", but I haven't managed to get incorrect-but-compiling code out of the above macros. Of course, I'm not at all convinced that it's faster than asprintf... (even after the obvious optimizations.)
This is not valid C99. In C99, arrays must have at least one element. Also, what is err.h? Whatever it is, it is not C99.
As for speed, if asprintf() does something clever to avoid rendering the string twice, this is actually slower unless snprintf() is faster than a malloc() call (unlikely). Furthermore, some compilers implement variable-length arrays with malloc() so for these, this is definitely not an improvement.
Beyond the speed of this particular call, using variable-length arrays can have a performance hit in general since gcc is unable to inline functions using them.
(Unfortunately asprintf isn't C99; but you can construct it easily out of vsnprintf: http://code.google.com/p/libcperciva/source/browse/trunk/uti...)