I would like to be able to pass formatted strings to other functions, like my error handler:
error_handler(str_format("error code %d in graphics function: %s", code, error));
My first idea was to use sprintf, but it does not return the result, so you have to add extra lines:
char msg = [100];
sprintf(msg, "error code %d in graphics function: %s", code, error)
error_handler(msg);
And if the error message is bigger than the size of msg then there could be problems.
So I thought I could make a wrapping function like this:
char* str_format (char* format, ...) {
char* output = malloc(100);
va_list args;
va_start(args, format);
vsnprintf(output, 100, format, args);
va_end(args);
return output;
}
But I still have the problem of not knowing the needed size for output. So I thought I could use sizeof to get the size of format and all the args, but there's no way to detect how many args there are unless you manually pass a number of args every time. I could read the format string and look for '%' symbols and detect each placeholder and type, but this seems to be too complicated, just to get the return value of sprintf.
I know there is a function called snprintf that will be safe and limit the number of chars but there is no vsnprintf in c
EDIT: #1 changed output to malloc #2 there is a vsnprintf function after all, so I changed to that. So then the question is down to if there is an easier way than this type of wrapper function. Thank you
Is there an easy way to get sprintf to return the formatted string?
CodePudding user response:
Typically, you use two calls to vsnprintf for this -- the first call with no buffer to get the size needed and a second call to fill the buffer
char* str_format (char* format, ...) {
va_list args;
va_start(args, format);
int len = vsnprintf(0, 0, format, args);
va_end(args);
char *output = malloc(len 1);
va_start(args, format);
vsnprintf(output, len 1, format, args);
va_end(args);
return output;
}
Note that the caller will need to free the allocated pointer (or it will leak)
CodePudding user response:
So I thought I could make a wrapping function like this:
/* !!! DON'T DO THIS !!! */ char* str_format (char* format, ...) { char output[100]; va_list args; va_start(args, format); vsprintf(output, format, args); va_end(args); return output; }
This is bad for multiple reasons: first, as you say you don't know the size of the output. Most importantly however, you cannot possibly return a variable defined locally (like output).
To know the size of the output, you can first make a "dummy" call to vsnprintf with a size of 0, which according to the manual should simply calculate and return the needed size for the output buffer.
As per returning a valid pointer, you could make this work in two different ways:
Allocate a buffer with
malloc, then return a pointer to the allocated buffer. This could be annoying since you would need to alwaysfree()the allocated buffer, and since you want to use your function likeerror_handler(str_format(...))you lose reference to the returned buffer, so you eitherfree()it inerror_handler()and always assume thaterror_handler()will takemalloc'd objects, or you need a temporary variable.Use a
staticbuffer. This however has the problem of needing to be pre-allocated with a constant size at compile time. You could choose this option and limit your error message size. One other limitation of this approach is that only one error at a time can be formatted, as you would be overwriting the samestaticbuffer each time. This is a pretty common among C libraries as it is painless and easy to implement, but only really applicable if you have reasonably-sized error messages.
Option 1 (error checking omitted):
char* str_format(char* format, ...) {
char *buf;
int size;
va_list args;
va_start(args, format);
size = vsnprintf(NULL, 0, format, args) 1;
va_end(args);
buf = malloc(size);
va_start(args, format);
vsnprintf(NULL, size, format, args);
va_end(args);
return buf;
}
Option 2:
char* str_format(char* format, ...) {
static char buf[1024]; // fixed at compile time
va_list args;
va_start(args, format);
vsnprintf(NULL, 1024, format, args);
va_end(args);
return buf;
}
Note that for vsnprintf you need to define at least one of the following feature test macros, as specified by the manual:
#define _XOPEN_SOURCE 500
// or
#define _ISOC99_SOURCE
// or
#define _BSD_SOURCE // old glibc <= 2.19
