How to use a variadic argument for its own data structure? - c

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;
}

Related

Prepend variadic arguments and pass to another function

I am having some trouble with a variadic printf-style function that I wrote, which works correctly when there are no variadic arguments but not when there are, e.g:
do_print("TestName", ""); works.
do_print("TestName", "Key1:%s\r\nKey2:%d\r\nKey3:%s", "Bob", 4, "Alice"); doesn't
I have done some debugging and I believe the issue is that the variadic arguments themselves are not getting processed as they should be. I looked at some examples and similar questions, and that has led me to believe that I'm misusing va_start and va_end in one of the functions. The format string that I'm generating is correct, but I'm getting a repeat for the first variadic arg of the name itself and then garbage values for the remaining variadic arguments:
Message:
Name:TestName
ID:42
Key1:TestName
Key2:-1
Key3:��-�*
(should be:)
Message:
Name:TestName
ID:42
Key1:Bob
Key2:4
Key3:Alice
My thought was perhaps that I should eliminate a va_start / va_end and just pass the ap in directly, although in certain cases I call the fmt_print function directly so I'm not sure what I would pass in for ap then. That's what I was playing with fmt_print2 for, but right now that just segfaults and is even worse than the original.
And yes, I do need vasprintf. The code here is printf-style, but ultimately I do need the entire message in a single buffer because I then pass it to the write function, not a printf-style function, so fmt_print does need to do the work of combining everything into a single buffer.
Here is a full minimal reproducible example, which I've been testing here: https://www.onlinegdb.com/online_c_compiler
#define _GNU_SOURCE /* needed for vasprintf */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
static int fmt_print(const char *fmt, ...)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
va_list ap;
va_start(ap, fmt);
if ((len = vasprintf(&buf, fmt, ap)) < 0) {
va_end(ap);
return -1;
}
va_end(ap);
printf("FORMAT STRING: %s\n", fmt);
printf("Message:\n%s[END]", buf);
return res;
}
static int fmt_print2(va_list ap, const char *fmt, ...)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
//va_start(ap, fmt);
if ((len = vasprintf(&buf, fmt, ap)) < 0) {
va_end(ap);
return -1;
}
//va_end(ap);
printf("FORMAT STRING: %s\n", fmt);
printf("Message:\n%s[END]", buf);
return res;
}
#pragma GCC diagnostic pop
static void do_print(const char *name, const char *fmt, ...)
{
va_list ap;
char buf[200];
snprintf(buf, sizeof(buf), "%s%s%s", "Name:%s\r\nID:%d\r\n", fmt, "\r\n\r\n");
// send Name:%s\r\ID:%d\r\nKey1:%s\r\nKey2:%d\r\nKey3:%s fmt string + all args.
//fmt_print2(ap, buf, name, 42);
va_start(ap, fmt);
fmt_print(buf, name, 42);
va_end(ap);
}
int main()
{
do_print("TestName", "Key1:%s\r\nKey2:%d\r\nKey3:%s", "Bob", 4, "Alice");
// This is also valid:
fmt_print("Login", "Name:%s\r\nID:%d\r\nUsername:%s\r\nPassword:%s", "Login", 41, username, password);
return 0;
}
Could someone point me in the right direction of what approach is best suited for this?
As I said before in a comment, you can't do what you are trying to do the way you are trying to do it. The snprintf() in do_print() is trying to set a format string that requires two separate va_list values to process it. You will have to keep the two lots of formatting separate. And then, if necessary, combine the two strings.
This code works. It contains a lot of diagnostic printing which you can remove, but it showed me where things were going wrong. It includes the trailing \r\n\r\n as a separate operation.
/* SO 7274-3429 */
#define _GNU_SOURCE /* needed for vasprintf */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int fmt_print(const char *fmt1, va_list ap1, const char *fmt2, ...)
{
char *buf1;
char *buf2;
int len1;
int len2;
va_list ap2;
char *buffer;
printf("%s(): fmt1 = [[%s]]\n", __func__, fmt1);
len1 = vasprintf(&buf1, fmt1, ap1);
printf("%s(): buf1 = [[%s]]\n", __func__, buf1);
printf("%s(): fmt2 = [[%s]]\n", __func__, fmt2);
va_start(ap2, fmt2);
len2 = vasprintf(&buf2, fmt2, ap2);
va_end(ap2);
printf("%s(): buf2 = [[%s]]\n", __func__, buf2);
if (len1 < 0 || len2 < 0)
return -1;
printf("%s(): Format strings:\n[[%s]]\n[[%s]]\n", __func__, fmt1, fmt2);
printf("%s(): Message strings:\n[[%s]]\n[[%s]]\n[END]\n", __func__, buf1, buf2);
buffer = malloc(len1 + len2 + sizeof("\r\n\r\n"));
if (buffer == 0)
{
free(buf1);
free(buf2);
return -1;
}
strcpy(buffer, buf2);
strcpy(buffer + len2, buf1);
strcpy(buffer + len2 + len1, "\r\n\r\n");
printf("Full buffer: [[%s]]\n", buffer);
free(buf1);
free(buf2);
free(buffer);
return 0;
}
static void do_print(const char *name, const char *fmt, ...)
{
va_list ap;
char buf[] = "Name:%s\r\nID:%d\r\n";
va_start(ap, fmt);
fmt_print(fmt, ap, buf, name, 42);
va_end(ap);
}
int main(void)
{
do_print("TestName", "Key1:%s\r\nKey2:%d\r\nKey3:%s", "Bob", 4, "Alice");
return 0;
}
The output from it is:
fmt_print(): fmt1 = [[Key1:%s
Key2:%d
Key3:%s]]
fmt_print(): buf1 = [[Key1:Bob
Key2:4
Key3:Alice]]
fmt_print(): fmt2 = [[Name:%s
ID:%d
]]
fmt_print(): buf2 = [[Name:TestName
ID:42
]]
fmt_print(): Format strings:
[[Key1:%s
Key2:%d
Key3:%s]]
[[Name:%s
ID:%d
]]
fmt_print(): Message strings:
[[Key1:Bob
Key2:4
Key3:Alice]]
[[Name:TestName
ID:42
]]
[END]
Full buffer: [[Name:TestName
ID:42
Key1:Bob
Key2:4
Key3:Alice
]]
Note that this code frees the memory allocated by vasprintf().
Since you try to compile with -Wmissing-format-attribute, you will still get errors from a verbatim copy of this code. It will compile cleanly with your options if you add:
#if !defined(PRINTFLIKE)
#if defined(__GNUC__)
#define PRINTFLIKE(n, m) __attribute__((format(printf, n, m)))
#else
#define PRINTFLIKE(n, m) /* If only */
#endif /* __GNUC__ */
#endif /* PRINTFLIKE */
and change the function definition lines to:
static int PRINTFLIKE(3, 4) fmt_print(const char *fmt1, va_list ap1, const char *fmt2, ...)
and
static void PRINTFLIKE(2, 3) do_print(const char *name, const char *fmt, ...)
and in do_print(), modify the call to fmt_print() to take a literal format (eliminating the variable buf):
fmt_print(fmt, ap, "Name:%s\r\nID:%d\r\n", name, 42);
A lot of the time, the 'non-literal format' check is helpful and sensible, but there are times when it is counter-productive.
You simply forgot to allocate memory for your fmt_print function.
static int fmt_print2(const char *fmt, va_list ap);
static int fmt_print(const char *fmt, ...)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
va_list ap;
va_start(ap, fmt);
if ((len = vsnprintf(NULL, 0, fmt, ap)) < 0) {
va_end(ap);
return -1;
}
buf = malloc(len + 1);
printf("LEN = %d\n", len);
va_start(ap, fmt);
if(buf)
{
vsnprintf(buf, len, fmt, ap);
printf("FORMAT STRING: %s\n", fmt);
printf("Message:\n%s[END]", buf);
}
va_end(ap);
return res;
}
static int fmt_print1(const char *fmt, ...)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
va_list ap;
va_start(ap, fmt);
fmt_print2(fmt, ap);
va_end(ap);
return res;
}
static int fmt_print2(const char *fmt, va_list ap)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
va_list copy;
va_copy(copy, ap);
if ((len = vsnprintf(NULL, 0, fmt, ap)) < 0) {
va_end(ap);
return -1;
}
buf = malloc(len + 1);
if(buf)
{
vsnprintf(buf, len, fmt, copy);
printf("FORMAT STRING: %s\n", fmt);
printf("Message:\n%s[END]", buf);
}
free(buf);
return res;
}
int main()
{
fmt_print("Key1:%s\r\nKey2:%d\r\nKey3:%s", "Bob", 4, "Alice");
fmt_print("Key1:%s\r\nKey2:%d\r\nKey3:%s", "AAAA", 8, "BBBB");
}

Concatenating a pointer to a string literal in a Macro

I need to append the function name of the caller concatenated wit brackets at the beginning of the string so it looks like [<FUNCTION_NAME>] <string> but rather I am getting [name] <string> as an output...possibly it has to do with # stringifying the variable passed in but how do you get it to work?
#define FUN(STR) "["#STR"] "
void foo(const char *name, char *fmt, ...)
{
char buff[100] = {0};
va_list pList;
va_start(pList, fmt);
int ret = vsprintf(buff, fmt, pList);
va_end(pList);
char p[100] = {0};
sprintf (p, "%s%s", FUN(name), buff);
printf ("%s\n", p); // [name] Some string with value = 5
}
void bar()
{
foo(__func__, "Some string with value = %d", 5);
}
int main()
{
bar();
return 0;
}
Just put the brackets in printf format string.
printf("[%s]%s", name, buff)
But it sounds like a waste to have two buffers. Just have one buffer. Also, the code needs protection against overload. Needed checks need to be added to protect against overflowing the buffer. Also, prefer to use snprintf over sprintf.
#define MIN(a, b) ((a)<(b)?(a):(b))
#ifdef __GNUC__
#if __GNUC__ > 10
__attribute__((__access__(read_only, 1)))
#endif
__attribute__((__format__(__printf__, 2, 3)))
#endif
void foo(const char *name, char *fmt, ...)
{
char buff[10];
char *p = buff;
const char *const end = buff + sizeof(buff);
// Add bracket.
static_assert(sizeof(buff) > 0, "");
*p++ = '[';
// Add name.
const size_t namelen = strlen(name);
size_t free = end - p;
// Leave space for 0 either way.
const size_t tocopy = MIN(free - 1, namelen);
memcpy(p, name, tocopy);
p += tocopy;
// Add "] ". Wonder, maybe it should be <=, not sure.
if (p < end - 1) {
*p++ = ']';
}
if (p < end - 1) {
*p++ = ' ';
}
// Add printf stuff
free = end - p;
if (free > 1) {
// It's odd that there is no vfoo(..., va_list va); overload.
va_list va;
va_start(va, fmt);
const int r = vsnprintf(p, free + 1, fmt, va);
va_end(va);
(void)r;
assert(r >= 0); // unneeded, pedantic: check errors
} else {
// Och, we remember about it.
assert(p < end);
*p = 0;
}
printf("%s\n", buff);
}
why would a macro approach not work?
In short: Macros work on text. Variables do not exist for preprocessor, only other macros. # applies # operation on text name, so it becomes "name", in effect the call becomes;
sprintf (p, "%s%s", "[" "name" "] ", buff);
Adjacent "this" "that" string literals are concatenated together into one "thisthat", and %s just prints it all.

Implementing your own printf function in C. How do I implement pointers %p?

Working on a project with a friend, and we have to create our own printf function in C, we managed to print : c, s,d, etc.. (see code below). But Don't know how to print pointers.
What are the steps necessary to print pointers, what is needed to be able to receive a %p input from my printf function and print to the console the address, how do I deal with this.
I shared with you this screenshot to see what my teacher wants from me.
int ft_putchar(int c) {
char a[1];
a[0] = (char)c;
return write(0, a, 1);
}
static int ft_printf_aux(const char *fmt, va_list ap, int len);
static int ft_print_c(const char *fmt, va_list ap, int len) {
int c = va_arg(ap, int);
ft_putchar(c);
return ft_printf_aux(fmt, ap, len + 1);
}
static int ft_putnum(unsigned long long n, unsigned int base, const char *digits) {
int res = 1;
if (n >= base)
res += ft_putnum(n / base, base, digits);
ft_putchar(digits[n % base]);
return res;
}
static int ft_print_d(const char *fmt, va_list ap, int len) {
int n = va_arg(ap, int);
unsigned long long u;
if (n < 0) {
ft_putchar('-');
len++;
u = -(unsigned)n;
} else {
u = n;
}
len += ft_putnum(u, 10, "0123456789");
return ft_printf_aux(fmt, ap, len);
}
static int ft_print_o(const char *fmt, va_list ap, int len) {
unsigned int n = va_arg(ap, unsigned int);
len += ft_putnum(n, 8, "01234567");
return ft_printf_aux(fmt, ap, len);
}
static int ft_print_u(const char *fmt, va_list ap, int len) {
unsigned int n = va_arg(ap, unsigned int);
len += ft_putnum(n, 10, "0123456789");
return ft_printf_aux(fmt, ap, len);
}
static int ft_print_x(const char *fmt, va_list ap, int len) {
unsigned int n = va_arg(ap, unsigned int);
len += ft_putnum(n, 16, "0123456789abcdef");
return ft_printf_aux(fmt, ap, len);
}
static int ft_print_X(const char *fmt, va_list ap, int len) {
unsigned int n = va_arg(ap, unsigned int);
len += ft_putnum(n, 16, "0123456789ABCDEF");
return ft_printf_aux(fmt, ap, len);
}
static int ft_print_s(const char *fmt, va_list ap, int len) {
const char *s = va_arg(ap, const char *);
if (s == NULL) {
s = "(null)";
}
while (*s) {
ft_putchar(*s++);
len++;
}
return ft_printf_aux(fmt, ap, len);
}
typedef int (*ft_print_dispatch_f)(const char *fmt, va_list ap, int len);
static ft_print_dispatch_f const ft_print_dispatch[256] = {
['c'] = ft_print_c,
['d'] = ft_print_d,
['i'] = ft_print_d,
['o'] = ft_print_o,
['u'] = ft_print_u,
['x'] = ft_print_x,
['X'] = ft_print_X,
['s'] = ft_print_s,
};
static int ft_printf_aux(const char *fmt, va_list ap, int len) {
int c;
while (*fmt) {
c = (unsigned char)*fmt++;
if (c != '%') {
ft_putchar(c);
len++;
} else {
c = (unsigned char)*fmt++;
if (ft_print_dispatch[c] == NULL) {
if (c == '\0')
break;
ft_putchar(c);
len++;
} else {
return ft_print_dispatch[c](fmt, ap, len);
}
}
}
return len;
}
int ft_vprintf(const char *fmt, va_list ap) {
return ft_printf_aux(fmt, ap, 0);
}
int ft_printf(const char *fmt, ...) {
va_list ap;
int n;
va_start(ap, fmt);
n = ft_printf_aux(fmt, ap, 0);
va_end(ap);
return n;
}
int main(void) {
ft_printf("Hello word\n");
ft_printf("%cello %s\n", 'H', "word");
ft_printf("%d == 0%o == 0x%x == 0x%X\n", 1, 1, 1, 1);
ft_printf("%d == 0%o == 0x%x == 0x%X\n", 123, 123, 123, 123);
ft_printf("%d == 0%o == 0x%x == 0x%X\n", 0xdead, 0xdead, 0xdead, 0xdead);
return 0;
}```
"%p" matches a void *, not just any pointer.
const void *ptr = va_arg(ap, void *);
Convert to an integer with (u)intptr_t from <stdint.h>
uintptr_t uptr = (uintptr_t) ptr;
Now print the integer as you like. Format for pointers is an implementation detail.
// Sample
pad_width = (sizeof uptr * CHAR_BIT + 3)/4;
len += ft_putnum_with_padding(uptr, 16, "0123456789ABCDEF", pad_width);

va_list misbehavior on Linux

I have some code that converts variadic parameters into a va_list, then passes the list on to a function that then calls vsnprintf. This works fine on Windows and OS X, but it is failing with odd results on Linux.
In the following code sample:
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
char *myPrintfInner(const char *message, va_list params)
{
va_list *original = &params;
size_t length = vsnprintf(NULL, 0, message, *original);
char *final = (char *) malloc((length + 1) * sizeof(char));
int result = vsnprintf(final, length + 1, message, params);
printf("vsnprintf result: %d\r\n", result);
printf("%s\r\n", final);
return final;
}
char *myPrintf(const char *message, ...)
{
va_list va_args;
va_start(va_args, message);
size_t length = vsnprintf(NULL, 0, message, va_args);
char *final = (char *) malloc((length + 1) * sizeof(char));
int result = vsnprintf(final, length + 1, message, va_args);
printf("vsnprintf result: %d\r\n", result);
printf("%s\r\n", final);
va_end(va_args);
return final;
}
int main(int argc, char **argv)
{
char *test = myPrintf("This is a %s.", "test");
char *actual = "This is a test.";
int result = strcmp(test, actual);
if (result != 0)
{
printf("%d: Test failure!\r\n", result);
}
else
{
printf("Test succeeded.\r\n");
}
return 0;
}
The output of second vsnprintf call is 17, and the result of strcmp is 31; but I don't get why vsnprintf would return 17 seeing as This is a test. is 15 characters, add the NULL and you get 16.
Related threads that I've seen but do not address the topic:
Pass va_list or pointer to va_list?
Passing one va_list as a parameter to another
With #Mat's answer (I am reusing the va_list object, which is not allowed), this comes squarely around to the first related thread I linked to. So I attempted this code instead:
char *myPrintfInner(const char *message, va_list params)
{
va_list *original = &params;
size_t length = vsnprintf(NULL, 0, message, params);
char *final = (char *) malloc((length + 1) * sizeof(char));
int result = vsnprintf(final, length + 1, message, *original);
printf("vsnprintf result: %d\r\n", result);
printf("%s\r\n", final);
return final;
}
Which, per the C99 spec (footnote in Section 7.15), should work:
It is permitted to create a pointer to a va_list and pass that pointer
to another function, in which case the original function may make
further use of the original list after the other function returns.
But my compiler (gcc 4.4.5 in C99 mode) gives me this error regarding the first line of myPrintfInner:
test.c: In function ‘myPrintfInner’:
test.c:8: warning: initialization from incompatible pointer type
And the resulting binary produces the exact same effect as the first time around.
Found this: Is GCC mishandling a pointer to a va_list passed to a function?
The suggested workaround (which wasn't guaranteed to work, but did in practice) is to use arg_copy first:
char *myPrintfInner(const char *message, va_list params)
{
va_list args_copy;
va_copy(args_copy, params);
size_t length = vsnprintf(NULL, 0, message, params);
char *final = (char *) malloc((length + 1) * sizeof(char));
int result = vsnprintf(final, length + 1, message, args_copy);
printf("vsnprintf result: %d\r\n", result);
printf("%s\r\n", final);
return final;
}
As Mat notes, the problem is that you're reusing the va_list. If you don't want to restructure your code as he suggests, you can use the C99 va_copy() macro, like this:
char *myPrintfInner(const char *message, va_list params)
{
va_list copy;
va_copy(copy, params);
size_t length = vsnprintf(NULL, 0, message, copy);
va_end(copy);
char *final = (char *) malloc((length + 1) * sizeof(char));
int result = vsnprintf(final, length + 1, message, params);
printf("vsnprintf result: %d\r\n", result);
printf("%s\r\n", final);
return final;
}
On compilers that don't support C99, you may be able use __va_copy() instead or define your own va_copy() implementation (which will be non-portable, but you can always use compiler / platform sniffing in a header file if you really need to). But really, it's been 13 years — any decent compiler should support C99 these days, at least if you give it the right options (-std=c99 for GCC).
The problem is that (apart from the missing return statement) you're re-using the va_list parameter without resetting it. That's not good.
Try something like:
size_t myPrintfInnerLen(const char *message, va_list params)
{
return vsnprintf(NULL, 0, message, params);
}
char *myPrintfInner(size_t length, const char *message, va_list params)
{
char *final = (char *) malloc((length + 1) * sizeof(char));
int result = vsnprintf(final, length + 1, message, params);
printf("vsnprintf result: %d\r\n", result);
printf("%s\r\n", final);
return final;
}
char *myPrintf(const char *message, ...)
{
va_list va_args;
va_start(va_args, message);
size_t length = myPrintfInnerLen(message, va_args);
va_end(va_args);
va_start(va_args, message);
char *ret = myPrintfInner(length, message, va_args);
va_end(va_args);
return ret;
}
(And turn on your compiler's warnings.)
I don't think the footnote you point to means what you think it does. I read it as: if you pass a va_list directly (as a value, not a pointer), the only thing you can do in the caller is va_end it. But if you pass it as a pointer, you could, say, call va_arg in the caller if the callee didn't "consume" all the va_list.
You could try with va_copy though. Something like:
char *myPrintfInner(const char *message, va_list params)
{
va_list temp;
va_copy(temp, params);
size_t length = vsnprintf(NULL, 0, message, temp);
...

Does a function like this exist? void str_realloc_and_concat(char *str, const char *format, ...)

I'm wondering if such a function exists:
void str_realloc_and_concat(char *str, const char *format, ...)
This function would take a char *str (allocated or NULL), and append to it *format.
I'm looking for something like a sprintf with realocation, strcpy and concatenation.
Does it exist or do I have to code it? Thanks for your inputs.
Update
The library has to be used on a embedded device so I don't want to use GNU extensions, since I'm not sure I'll have them.
Here is an implementation of such a function. To my knowledge the standard C library does not contain a function such as the one you are looking for. Notice that I would recommend to pass the target string as a double pointer, since realloc may change the pointer:
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
const char *prefix = "Hello";
void str_realloc_and_concat(char **str, const char *format, ...)
{
va_list ap;
int i, n;
char *s;
n = strlen(*str);
va_start(ap, format);
i = vasprintf(&s, format, ap);
*str = (char *)(realloc(*str, n + i + 1));
if (*str != NULL)
strncpy(&(*str)[n], s, i);
va_end(ap);
}
int main()
{
char *s = (char *)(malloc(strlen(prefix)));
strncpy(s, prefix, strlen(prefix));
str_realloc_and_concat(&s, " %s", "world!");
printf("%s\n", s);
return 0;
}
I decided to go with this. Hope it will help.
/**
* Reallocate a string and concatenate it with a formatted string.
* The src string has to be allocated by malloc/calloc.
* #param src Pointer to the original string
* #param format Format string
* #param ... Arguments that are to be formatted
*/
char *strcatf(char *src, const char *format, ...)
{
va_list ap, cp;
int format_length;
size_t translation_length;
char *dest;
if (NULL == src) {
return NULL;
}
va_start(ap, format);
va_copy(cp, ap);
translation_length = strlen(src);
format_length = vsnprintf(NULL, 0, format, cp);
if ((dest = realloc(src, (translation_length + format_length + 1) * sizeof(char))) == NULL) {
free(src);
} else {
vsprintf(&(dest)[translation_length], format, ap);
}
va_end(ap);
va_end(cp);
return dest;
}
This does not have the realloc part, but it does allocate. GNU libc's asprintf is like sprintf but allocates the result (to be big enough).
char *res = NULL;
const char *str = "other string";
int ret = asprintf(&res, "Hi %d %s\n", 2, str);
Concatenation is just a special case of using the format string.. if I understand correctly.

Resources