I would like to design a function which takes a variable number of arguments, one of these arguments being itself a va_list
; but something gets wrong in my code and I do not understand what...
WARNING — My question is not about designing a code doing what I wish to do (I have found a way to bypass the problem), but only about understanding what I did wrong...
To explain my question, let us start with a simple example: namely, a function ffprintf
which acts like fprintf
, but writing its contents onto several strings, strings whose number is indicated by the first argument of ffprintf
, and whose identities are given by the very next arguments (the number of these arguments may vary from one call to another, so you have to use a variable argument list). Such a function would be used like this:
FILE *stream0, *stream1, *stream2;
int a, b;
ffprintf (3, stream0, stream1, stream2, "%d divided by %d worths %f", a, b, (double)a / b);
And its code would be:
void ffprintf (int z, ...)
{va_list vlist, auxvlist;
FILE **streams = malloc (z * sizeof(FILE *));
va_start (vlist, z);
for (int i = 0; i < z; ++i)
{streams[i] = va_arg (vlist, FILE *); // Getting the next stream argument
}
char const *format = va_arg (vlist, char const *); // Getting the format argument
for (int i = 0; i < z; ++i)
{va_copy (auxvlist, vlist); // You have to work on a copy "auxvlist" of "vlist", for otherwise "vlist" would be altered by the next line
vfprintf (streams[i], format, auxvlist);
va_end (auxvlist);
}
va_end (vlist);
free (streams);
}
That works fine. Now, there is also the standard function vfprintf
, whose prototype is vfprintf (FILE *stream, char const* format, va_list vlist);
, and which you use like this to create another function having a variable argument list:
void fprintf_variant (FILE *stream, char const* format, ...)
{
va_list vlist;
va_start (vlist, format);
vfprintf (stream, format, vlist);
va_end (vlist);
}
That works fine too. Now, my goal is to combine both ideas to create a function which I would call vffprintf
, which you would use like this:
FILE *stream0, *stream1, *stream2;
void fprintf_onto_streams012 (char const *format, ...)
{va_list vlist;
va_start (vlist, format);
vffprintf (3, stream0, stream1, stream2, format, vlist);
va_end (vlist);
}
I have designed the following code:
void vffprintf (int z, ...)
{va_list vlist, auxvlist, auxauxvlist;
va_start (vlist, z);
FILE **streams = malloc (z * sizeof(FILE *));
for (int i = 0; i < z; ++i)
{streams[i] = va_arg (vlist, FILE *);
}
char const *format = va_arg (vlist, char const *);
va_copy (auxvlist, va_arg (vlist, va_list)); // Here I get the next argument of "vlist", knowing that this argument is of "va_list" type
for (int i = 0; i < z; ++i)
{va_copy (auxauxvlist, auxvlist);
vfprintf (streams[i], format, auxvlist);
va_end (auxauxvlist);
}
va_end (auxvlist);
va_end (vlist);
free (streams);
}
This code compiles without a hitch, but it does not work properly... For instance, if I write the following complete code:
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
void vffprintf (int z, ...)
{va_list vlist, auxvlist, auxauxvlist;
FILE **streams = malloc (z * sizeof(FILE *));
va_start (vlist, z);
for (int i = 0; i < z; ++i)
{streams[i] = va_arg (vlist, FILE *);
}
char const *format = va_arg (vlist, char const *);
va_copy (auxvlist, va_arg (vlist, va_list));
for (int i = 0; i < z; ++i)
{va_copy (auxauxvlist, auxvlist);
vfprintf (streams[i], format, auxauxvlist);
va_end (auxauxvlist);
}
va_end (auxvlist);
va_end (vlist);
free (streams);
}
void printf_variant (char const *format, ...)
{va_list vlist;
va_start (vlist, format);
vffprintf (1, stdout, format, vlist);
va_end (vlist);
}
int main (void)
{printf_variant ("Ramanujan's number is %d.\n", 1729);
return 0;
}
I get a segfault!... Why?!
P.-S.: Sorry for that very long question; but I wanted it to be perfectly clear, for it is rather technical...
P.-S.2: I used deliberately both tags "va-list" and "variableargumentlists" for this question, because which interests me is va_list
, seen as a type, inside a (other) variable argument list, seen as a list... So these are really two different concepts here.
The description of va_arg
in the final draft of C11 (N1570) contains (type is the second argument):
if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined
va_list
is allowed to be an array type (the standard requires it to be a so-called “complete object type”) and it seems your implementation makes use of this possibility. You probably know that in C arrays can't be passed as arguments as they decay into pointers, and the type of such a pointer isn't compatible with the original array type.
For example: int *
isn't compatible with int [1]
. So if you really need to pass an array or a va_list
portably, then define a struct
with a va_list
member and pass that (see Why can't we pass arrays to function by value?).
void vffprintf (int z, ...)
{
//...
va_copy (auxvlist, va_arg (vlist, va_list));//this line has the problem
//...
}
Just a quick and tricky way like this, it will work.
void vffprintf (int z, ...)
{
//...
va_copy (auxvlist, va_arg (vlist, void*));
//...
}
Here are some references about var_arg
and va_list
, which should have provided detailed and thorough explanation.
1) Pass va_list or pointer to va_list?
2) Is GCC mishandling a pointer to a va_list passed to a function?
3) What is the format of the x86_64 va_list structure?
Hope they are helpful.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With