Of course, it's not hard to produce a "close enough to work" estimate for this particular type of example: %d takes up a max of 12 characters, %s takes up as much as the string is long, and then the format string's length.
You could write a parser that takes the arguments, like this:
Of course, it would get a whole lot more complicated if you needed to cope with more exotic variants of formatting, e.g "%10d" or "%10.19f", or worse yet "%*d". It can be dealt with, but at that point, it may make more sense to use a really large buffer, sprintf() to that buffer, and then copy to a smaller buffer if you need to keep the string around.
int sprintfalloc(const char *fmt, ...)
const char *s = fmt;
size_t size = 0;
if (*s == '%')
size += strlen((va_arg(argp, char *));
size += 12; // We could calculate the size of course.
(void) va_arg(argp, int);
// Do something here, perhaps?
str = malloc(size + 1);
vsprintf(str, fmt, argp);