To facilitate the use of a data structure that contains a string to be filled from a function, I would like to be able to define the same function with variadic arguments, like this:
struct my_struct_t
{
char *msg;
};
struct my_struct_t *fill(const char *fmt, ...);
struct my_struct_t *filled = fill("A number: %d, a string: '%s'.", 43, "hello");
To do so, I implemented the following function, as well as variants, but the result was always wrong when I retrieved the string in the structure. Here is the code:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
struct my_struct_t
{
char *msg;
};
struct my_struct_t *fill(const char *fmt, ...)
{
va_list ap1, ap2;
va_copy(ap2, ap1);
va_start(ap1, fmt);
int slen = snprintf(NULL, 0, fmt, ap1);
va_end(ap1);
char *str = malloc(slen);
assert(str != NULL);
va_start(ap2, fmt);
snprintf(str, slen, fmt, ap2);
va_end(ap2);
struct my_struct_t *my_struct = malloc(sizeof *my_struct);
assert(my_struct != NULL);
my_struct->msg = str;
return my_struct;
}
int main()
{
struct my_struct_t *filled = fill("A number: %d, a string: '%s'.", 43, "hello");
printf("%s\n", my_struct->msg);
return 0;
}
This leads to different results with each execution, for example:
A number: 7011816, a string: 'ðe'
I guess this is a problem with the use of variadic arguments, however I haven't found how to solve my problem, i.e. save the string in the structure field with the formatting sent, so I'll expect this:
A number: 43, a string: 'hello'.
A quick possible fix. Notes in code
struct my_struct_t *fill(const char *fmt, ...) {
va_list ap1, ap2;
// change order
va_start(ap1, fmt);
va_copy(ap2, ap1);
// Use vsnprintf
//int slen = snprintf(NULL, 0, fmt, ap1);
int slen = vsnprintf(NULL, 0, fmt, ap1);
va_end(ap1);
// test result
assert(slen >= 0);
// ... or a pedantic test
assert(slen >= 0 && (unsigned) slen < SIZE_MAX);
// Need + 1 for null character
// char *str = malloc(slen);
char *str = malloc(slen + 1u);
assert(str != NULL);
// No va_start, copy is enough
// va_start(ap2, fmt);
// snprintf(str, slen, fmt, ap2);
// Since we a going for broke, no need for `n`, pedantically we could/should use vsnprintf()
// vsnprintf(str, slen+1u, fmt, ap2);
vsprintf(str, fmt, ap2);
va_end(ap2);
// Good use of sizing by referenced type
struct my_struct_t *my_struct = malloc(sizeof *my_struct);
assert(my_struct != NULL);
my_struct->msg = str;
return my_struct;
}
Rather than assert()
, code could return NULL
. Be sure to free resources.
struct my_struct_t *fill(const char *fmt, ...) {
va_list ap1, ap2;
va_start(ap1, fmt);
va_copy(ap2, ap1);
int slen = vsnprintf(NULL, 0, fmt, ap1);
va_end(ap1);
if (slen < 0 || (unsigned) slen >= SIZE_MAX) {
va_end(ap2);
return NULL;
}
char *str = malloc(slen + 1u);
if (str == NULL) {
va_end(ap2);
return NULL;
}
slen = vsnprintf(str, slen+1u, fmt, ap2);
va_end(ap2);
if (slen < 0 || (unsigned) slen >= SIZE_MAX) {
free(str);
return NULL;
}
struct my_struct_t *my_struct = malloc(sizeof *my_struct);
if (my_struct == NULL) {
free(str);
return NULL;
}
my_struct->msg = str;
return my_struct;
}