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.
You might need to wrap the type va_list into a struct, if you want to retrieve it using va_arg():
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
typedef struct
{
va_list list ;
} my_list ;
void vffprintf (int z, ...)
{my_list vlist, auxvlist, auxauxvlist;
FILE **streams = malloc (z * sizeof(FILE *));
va_start (vlist.list, z);
for (int i = 0; i < z; ++i)
{streams[i] = va_arg (vlist.list, FILE *);
}
char const *format = va_arg (vlist.list, char const *);
my_list parent = va_arg (vlist.list, my_list) ;
va_copy (auxvlist.list, parent.list );
for (int i = 0; i < z; ++i)
{va_copy (auxauxvlist.list, auxvlist.list);
vfprintf (streams[i], format, auxauxvlist.list);
va_end (auxauxvlist.list);
}
va_end (auxvlist.list);
va_end (vlist.list);
free (streams);
}
void printf_variant (char const *format, ...)
{my_list vlist;
va_start (vlist.list, format);
vffprintf (1, stdout , format, vlist);
va_end (vlist.list);
}
int main (void)
{printf_variant ("Ramanujan's number is %d.\n", 1729 );
return 0;
}
The problem stems from the fact that array and a pointer of the same type are not compatible, and va_list is defined as an array. Then you try to get that type:
va_arg (vlist, va_list)
So you tell va_arg you are getting an array but if fact the passed va_list has decayed to a pointer. You should use the pointer version of va_list, but you don't know the real definition of va_list, in the first place so you cannot obtain the pointer version of it.
The solution is to wrap va_list into a type you control, a struct.
Related
I run this in MSVC v142.
I need to keep multiple va_list and then pass all of them in an array to another API.
However, the old va_list were getting overwritten by the newer one.
void toVaList(va_list *out, int count, ...)
{
va_list args;
va_start(args, count);
vprintf("toVaList: %d %d\n", args);
*out = args;
}
int main()
{
va_list args1;
toVaList(&args1, 2, 11, 22);
vprintf("first va_list: %d %d\n", args1);
va_list args2;
toVaList(&args2, 2, 33, 44);
vprintf("first va_list: %d %d\n", args1); //now args1 are overwritten
vprintf("second va_list: %d %d\n", args2);
va_end(args1);
va_end(args2);
return 0;
}
The output is
toVaList: 11 22
first va_list: 11 22
toVaList: 33 44
first va_list: 33 44
second va_list: 33 44
Is it possible to resolve this issue?
Edit:
Now memcpy works for me. I copied the va_list to a separate memory and kept the original va_list in the function body wrapped by va_start and va_end. It looks like the copied va_list outside of the function body works well.
vprintf doesn't invalidate the va_list args, since it might have made a va_copy inside its implementation.
void toVaList1(va_list *out, int count, ...)
{
va_list args;
va_start(args, count);
memcpy(out, args, 256);
vprintf("toVaList1: %d %d\n", args);
va_end(args);
}
int main()
{
char args1[256];
toVaList1((va_list *)args1, 2, 11, 22);
vprintf("first va_list: %d %d\n", args1);
char args2[256];
toVaList1((va_list *)args2, 2, 33, 44);
vprintf("first va_list: %d %d\n", args1);
vprintf("second va_list: %d %d\n", args2);
char args3[256];
toVaList1((va_list *)args3, 2, 55, 66);
vprintf("first va_list: %d %d\n", args1);
vprintf("second va_list: %d %d\n", args2);
vprintf("third va_list: %d %d\n", args3);
}
The output is
toVaList1: 11 22
first va_list: 11 22
toVaList1: 33 44
first va_list: 11 22
second va_list: 33 44
toVaList1: 55 66
first va_list: 11 22
second va_list: 33 44
third va_list: 55 66
Edit:
Briefly describe the scenario for this requirement.
We have a list of messages with id defined in XML, and an internal API that takes msgId and va_list as arguments, which uses va_arg() to read va_list:
ID Message
1 This is a message containing integer %d and string %s.
2 This is another message containing double %f.
.....
void internalAPI_PrintMsg(int msgId, va_list ap)
{
char resultMsg[512];
//lookup message id in XML and substitute arguments
//in the loop through found message body...
switch(char)
{
case 'd':
int i = va_arg(ap, int);
//substitute the integer
break;
case 'f':
double d = va_arg(ap, double);
//substitute the double
break;
....
}
PrintResultMessage(resultMsg);
}
We already have API to print single message:
void printMsg(int msgId, ...)
{
va_list args;
va_start(args, msgId);
internalAPI_PrintMsg(msgId, args);
va_end(args);
}
Now we need to support an API to print a group of messages:
typedef struct
{
long id;
char[256] args;
} msgData;
void printGroupMsg(msgData * msgArr, int msgCount) //this is the new API that requires multiple va_list
{
//do some other stuff to start group messaging
for (int i = 0; i < msgCount; ++i)
internalAPI_PrintMsg(msgArr[i].id, msgArr[i].args);
//do some other stuff to end group messaging
}
void initMsgData(msgData *msg, int msgId, ...) //this is equivalent to toVaList(...)
{
msg->id = msgId;
va_list args;
va_start(args, msgId);
memcpy(msg->args, args, 256);
va_end(args);
}
//client code:
msgData *arr = malloc(sizeof(msgData)*2);
initMsgData(arr, 1, 88, "test");
initMsgData(arr+1, 2, 123.456);
printGroupMsg(arr, 2);
Note that the C standard stipulates that if you invoke va_start in function, you must also invoke va_end in the same function:
§7.16.1 Variable argument list access macros
¶1 The va_start and va_arg macros described in this subclause shall be implemented as macros, not functions. It is unspecified whether va_copy and va_end are macros or identifiers declared with external linkage. If a macro definition is suppressed in order to access an actual function, or a program defines an external identifier with the same name, the behavior is undefined. Each invocation of the va_start and va_copy macros shall be matched by a corresponding invocation of the va_end macro in the same function.
[…Emphasis added…]
Your toVaList function is violating that requirement and therefore invokes undefined behaviour.
The preamble to §7.16 says:
3 The type declared is
va_list
which is a complete object type suitable for holding information needed by the macros va_start, va_arg, va_end, and va_copy. If access to the varying arguments is desired, the called function shall declare an object (generally referred to as ap in this subclause) having type va_list. The object ap may be passed as an argument to another function; if that function invokes the va_arg macro with parameter ap, the value of ap in the calling function is indeterminate and shall be passed to the va_end macro prior to any further reference to ap.253)
253) 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.
va_copy must be used in place of *out = args; to makes a copy of the va_list but it does not change the scope where it is valid to enumerate it, which is the called function. Referring to the va_list after the function toVaList returns has undefined behavior.
This restriction is not specified explicitly in the C Standard. The relevent language is this:
7.16.1 Variable argument list access macros
...
Each invocation of the va_start and va_copy macros shall be matched by a corresponding invocation of the va_end macro in the same function.
This phrase means that va_end() must be called in the same function that called va_start or va_copy for the same va_list argument, hence this argument cannot be used once the function returns.
Your approach has undefined behavior per the C Standard. This is easy to understand from an implementation standpoint: calling a function with a variable number of arguments involves passing these arguments to the function in an implementation defined way that the variable argument accessing macros abstracts. Yet like any other function call, the arguments go out of scope when the function returns to its caller. If you want to access them at some later point, you must enumerate them and save their values to an array or structure with an appropriate scope and life time. The memcpy hack attempts to do this, but its behavior is undefined.
Here is an alternative approach:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
int **array_from_args(int count, ...) {
va_list ap;
va_start(ap, count);
int *a = malloc(sizeof(*a) * (1 + count));
if (a) {
a[0] = count;
for (int i = 0; i < count; i++) {
a[i + 1] = va_arg(ap, int);
}
}
va_end(ap);
return a;
}
void print_array(const char *s, const int *a) {
printf("%s:", s);
if (a) {
for (int i = 0, n = a[0]; i < n; i++)
printf(" %d", a[i + 1]);
}
printf("\n");
}
int main() {
int *args1 = array_from_args(2, 11, 22);
int *args2 = array_from_args(2, 33, 44);
int *args3 = array_from_args(2, 55, 66);
print_array("args1", args1);
print_array("args2", args2);
print_array("args3", args3);
free(args1);
free(args2);
free(args3);
}
You could generalize this approach for arguments of different types by providing type information as an argument to the array_from_args(), like a printf format string, and construct an array of tagged structures.
If the format string passed to vsprintf() (and variants thereof) contains no %-references, is it guaranteed that the va_list argument is not accessed?
Put another way, is:
#include <stdarg.h>
#include <stdio.h>
int main ( void ) {
char str[16];
va_list ap; /* never initialized */
(void)vsnprintf(str, sizeof(str), "", ap);
return 0;
}
a standard-conforming program? or is there undefined behavior there?
The example above is obviously silly, but imagine a function which can be called by both a variadic function and a fixed-args function, grossly simplified into something like:
void somefuncVA ( const char * fmt, va_list ap ) {
char str[16];
int n;
n = vsnprintf(str, sizeof(str), fmt, ap);
/* potentially do something with str */
}
void vfoo ( const char * fmt, ... ) {
va_list ap;
va_start(fmt, ap);
somefuncVA(fmt, ap);
}
void foo ( void ) {
va_list ap; /* no way to initialize this */
somefuncVA("", ap);
}
int vsprintf(char * restrict s, const char * restrict format, va_list arg);
If the format string passed to vsprintf() ... contains no %-references, is it guaranteed that the va_list argument is not accessed.
No.
The vsprintf function is equivalent to sprintf, with the variable argument list
replaced by arg, which shall have been initialized by the va_start macro .....
C11dr §7.21.6.13
Since the below code does not adhere to the spec, the result is undefined behavior (UB). No guarantees. #Eugene Sh.
va_list ap;
// vv-- ap not initialized
(void)vsnprintf(str, sizeof(str), "", ap);
Is vsprintf() guaranteed not to access va_list if format string makes no % references?
With a properly passed va_list arg, vsprintf() acts like sprintf(). Code like the following is OK. It is permissible to pass extra arguments. Via vsprintf(), they (the extra arguments) are not accessed, yet va_list arg may be accessed.
sprintf(buf, "format without percent", 1.2345, 456)`
If you don't have varargs passed to your function - your function isn't defined with ... as the last parameter - there's simply never any need for any use of va_list or va_start() in that function. If you want to pass an empty set of variable arguments, simply call the varargs function directly without any variable arguments - e.g., printf("\n");.
For example, instead of
void foo ( void ) {
va_list ap; /* no way to initialize this */
somefuncVA("", ap);
}
you can just write
void foo ( void ) {
vfoo("");
}
In the program below, the variadic function process_message passes its variable arguments via the va_list argp parameter to print_message. argp is in turn passed to vsnprintf which calculates the length of the format string.
However, if argp is passed to a another function called from within print_message such as vprintf(fmt, argp), it produces nonsense output. Is it possible for a function taking va_list parameter to use it more than once?
#include <stdio.h>
#include <stdarg.h>
void process_message(const char *fmt, ...);
void print_message(const char *fmt, va_list argp);
int main(void) {
process_message("%s:%d\n", "test message", 1);
return 0;
}
void process_message(const char *fmt, ...) {
va_list argp;
va_start(argp, fmt);
print_message(fmt, argp);
va_end(argp);
}
void print_message(const char *fmt, va_list argp) {
/*Calculate and print the length of the format string*/
int len = vsnprintf(NULL, 0, fmt, argp);
printf("Length of format string = %d\n", len);
/*Print the rendered format string - this produces a nonsense output
*if argp was aleady passed to another function earlier */
vprintf(fmt, argp);
}
You can use va_copy:
The va_copy() macro copies the (previously initialized) variable argument list src to dest. The behavior is as if va_start() were applied to dest with the same last argument, followed by the same number of va_arg() invocations that was used to reach the current state of src.
You print_message() could be:
void print_message(const char *fmt, va_list argp) {
/*Calculate and print the length of the format string*/
va_list argp_copy;
va_copy(argp_copy, argp);
int len = vsnprintf(NULL, 0, fmt, argp_copy);
va_end(argp_copy);
printf("Length of format string = %d\n", len);
/*Print the rendered format string - this produces a nonsense output
*if argp was aleady passed to another function earlier */
vprintf(fmt, argp);
}
NOTE:
Don't forget to call va_end after using a copied va_list.
C11:
Each invocation of the va_start and va_copy macros shall be matched by a corresponding invocation of the va_end macro in the same function.
I want to do some exercise about va_list. This is my code.
int myscanf( char* fmt, ... ) {
va_list ap;
va_start ( ap, fmt );
vfscanf ( stdin, fmt, ap );
va_end ( ap );
}
int main() {
int a, b;
myscanf( "%d %d", &a, &b );
}
As I shown above, I have written a scanf() and it is work.
Now I want to redirect the value of arguments in myscanf().
For example, I can redirect fmt to the space which is allocated in myscanf()
int myscanf( char* fmt, ... ) {
char newFmt[10] = "%d %d";
va_list ap;
va_start ( ap, fmt );
vfscanf ( stdin, newFmt, ap );
va_end ( ap );
}
However, I feel confused when I try to change the value of others arguments.
I can fetch these variable argument by va_arg(), but I can't modify them because va_arg() is a macro.
int myscanf( char* fmt, ... ) {
va_list ap;
va_start ( ap, fmt );
int* arg1 = (int)va_arg(ap, int*); // get the value of &a in main()
int newA; // I want to read a value by scanf() and store it to &newA
// ??? = &newA // <- how to do?
vfscanf ( stdin, fmt, ap );
va_end ( ap );
}
Any suggestion?
-----------edit-----------
Thanks for replies,
But something should be clarified.
The "value" in this case is "address". Therefore, my purpose is changing the target address so that the vfscanf() will read and write the value to the another address space.
For example,
int gA, gB, gC, gD;
int myscanf( char* fmt, ... ) {
va_list ap;
va_start ( ap, fmt );
// do something for making the following vfscanf() to write value into gC and gD directly
vfscanf ( stdin, fmt, ap );
// don't assign *gA to *gC and *gB to *gD after performing vfscanf()
va_end ( ap );
}
int main() {
myscanf( "%d %d", &gA, &gB );
}
As I change fmt to newFmt, we want to change the value (in this case is address) in va_list directly.
And the parsing problem is solved because that I can allocate a space dynamically while I parse a "%..." from format string. These addresses of spaces will replace inputs repeatedly if the question above is solved.
Variadic Functions
The arguments to scanf will always be pointers, not values as in your example. The correct way of getting an argument of scanf would be int *arg1 = va_arg(ap, int*); - and you don't need to cast.
If you want to manipulate the way scanf behaves, you have to know first how variadic functions work (you can get it by reading the manual of any of the va_* family of functions). The variable ap in most architectures is a pointer to the function's stack frame. It points to the next variable after fmt in this case.
Your example
In the case of scanf in your example, it will point to a list of pointers (because all arguments to scanf must be pointers). So you should put that into your pointers like this:
int *a = va_arg(ap, int*);
/* Then you can modify it like this: */
*a = 666;
There are some problems with this.
When you finish manipulating the arguments, you must pass fmt and ap to vfscanf, which will then parse fmt and expect n elements (the amount of elements in the format string). The problem is that ap now will only give us n - x elements (x being the number of elements you "poped" in your own function). A little example:
myscanf("%d %d", &a, &b);
/* n = 2 */
...
int *a = va_arg(ap, int *);
/* x = 1 */
...
vfscanf(stdin, fmt, ap);
/* n = 2 cause fmt is still the same, however
* x = 1, so the number of elements "popable" from the stack is only
* n - x = 2 - 1 = 1.
*/
In this simple example you can already see the problem. vfscanf will call va_arg for each element it finds in the format string, which is n, but only n - x are popable. This means undefined behavior - vfscanf will be writing somewhere it shouldn't and most probably will crash your program.
Hack Around
To overcome that, I propose a little work with va_copy. The signature of va_copy is:
void va_copy(va_list dest, va_list src);
And something to know about it (from the manual):
Each invocation of va_copy() must be matched by a corresponding invocation of va_end() in the same function. Some systems that do not supply va_copy() have __va_copy instead, since that was the name used in the draft proposal.
The solution:
#include <stdio.h>
#include <stdarg.h>
int myscanf(char *fmt, ...)
{
va_list ap, hack;
/* start our reference point as usual */
va_start(ap, fmt);
/* make a copy of it */
va_copy(hack, ap);
/* get the addresses for the variables we wanna hack */
int *a = va_arg(hack, int*);
int *b = va_arg(hack, int*);
/* pass vfscanf the _original_ ap reference */
vfscanf(stdin, fmt, ap);
va_end(ap);
va_end(hack);
/* hack the elements */
*a = 666;
*b = 999;
}
int main(void)
{
int a, b;
printf("Type two values: ");
myscanf("%d %d", &a, &b);
printf("Values: %d %d\n", a, b);
return 0;
}
Conclusion and Warnings
There are a couple of things you should note. First, if you put the hacking of the elements before calling vfscanf, the values you set will be lost, because vfscanf will overwrite those locations.
Next, you should also note that this is a very specific use case. I knew beforehand that I was going to pass two integers as arguments, so I designed myscanf with that in mind. But this means you need a parsing pass to find out which arguments are of which type - if you don't do it, you'll enter undefined behavior again. Writing that kind of parser is very straight-forward and shouldn't be a problem.
After your edit
After what you said in your clarification edit, I can only propose a little wrapper function around vfscanf(), because you can't directly manipulate va_list variables. You can't write directly to the stack (in theory, you can't, but if you did some inline-assembly you could, but that's gonna be an ugly hack and very non-portable).
The reason it's gonna be extremely ugly and non-portable is that the inline assembly will have to take into account how the architecture treats argument passing. Writing inline-assembly by itself is already very ugly... Check out this for the official GCC manual on it's inline assembly.
Back to your problem:
Stack Overflow: How do I fill a va_list
That answer explains a whole lot, so I won't say it here again. The final conclusion of the answer is **no, you don't do it". What you _can do however, is a wrapper. Like this:
#include <stdio.h>
#include <stdarg.h>
int a, b, c, d;
void ms_wrapper(char *newfmt, ...)
{
va_list ap;
va_start(ap, newfmt);
vfscanf(stdin, newfmt, ap);
va_end(ap);
}
int myscanf(char *fmt, ...)
{
/* manipulate fmt.... */
char *newfmt = "%d %d";
/* come up with a way of building the argument list */
/* call the wrapper */
ms_wrapper(newfmt, &c, &d);
}
int main(void)
{
a = 111;
b = 222;
c = 000;
d = 000;
printf("Values a b: %d %d\n", a, b);
printf("Values c d: %d %d\n\n", c, c);
printf("Type two values: ");
myscanf("%d %d", &a, &b);
printf("\nValues a b: %d %d\n", a, b);
printf("Values c d: %d %d\n", c, d);
return 0;
}
Beware that you can only build argument lists for variadic functions in your compile-time. You can't have a dynamically changing list of parameters. In other words, you'll have to hard-code each case you'd ever wanna handle. If the user enters something different, your program will behave very oddly and most probably crash.
The only way is to pass updated arguments directly, since va_list can not be modified. In your case you should parse format string to have an idea about actual content of va_list and then pass compatible set of arguments to fscanf() (not vfscanf()) directly.
It is not possible directly but you can do as below.
int myscanf( char* fmt, ... ) {
va_list ap;
va_start ( ap, fmt );
int newA;
scanf("%d",&new);
vfscanf ( stdin, fmt, ap );
va_end ( ap );
}
I think this will do same as you want.
On a given platform you may use some tricky hack:
va_list is basically a pointer to some data (typically char *),
va_arg is basically pointer arithmetic and cast
So, you can allocate an array of two pointers to int, set the values and call vfscanf with it. Something like:
int *hack[2];
hack[0] = &gC;
hack[1] = &gD;
vscanf(stdin, fmt, (va_list)hack);
BEWARE this is highly non portable, very tricky and error prone. There is a lot of problem with such, even if it basically works on many platforms.
Background: I am currently trying to "extend" standard C formatting with support for handling a certain struct, similar to how Objective-C extends C formatting to allow support for NSString with the "%#" sequence.
The one problem I'm struggling with is that vsprintf seems to be behaving differently on OS X versus Linux (I've tested with Ubuntu 10.10 and 12.04). On OS X, it is behaving how I thought it should, where after calling vsprintf, calling va_arg returns the ms pointer (as if the vsprintf function called va_arg to get the 5). On Linux, however, the va_list does not change from vsprintf, and calling va_arg returns 5.
I would really like to figure out a way to implement this functionality so that it behaves consistently across platforms. Is it wrong to assume that you can expect vsprintf to consistently change the pointer inside va_list so that the next time you call va_arg it returns the next not-yet-used argument?
I have simplified my code as much as possible to demonstrates the issue. On OS X, this code prints the correct address of the pointer returned from malloc. On Linux, the value of ms in foo becomes 5, so it prints 5.
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
static void foo(void *, ...);
typedef struct {
char *value;
} mystruct;
int main(int argc, char *argv[]) {
mystruct *ms = malloc(sizeof(mystruct));
foo(NULL, "%d %#", 5, ms);
}
void foo(void *dummy, ...) {
va_list args;
va_start(args, dummy);
char buffer[512];
int buffer_ptr = 0;
int i = 0;
char *format = va_arg(args, char *);
buffer[0] = '\0';
for (i = 0; i < strlen(format); i++) {
if (i <= strlen(format) - 1 && (format[i] == '%' && format[i+1] == '#')) {
vsprintf(buffer, buffer, args);
/* can expect the next argument to be a mystruct pointer */
mystruct *ms = va_arg(args, mystruct *);
buffer[buffer_ptr+1] = '\0';
fprintf(stderr, "%p", ms); /* SHOULD NOT PRINT 5 */
/* concatenate here */
} else {
buffer[buffer_ptr++] = format[i];
buffer[buffer_ptr] = '\0';
}
}
va_end(args);
}
You need to use va_copy if you're going to use an argument list more than once -- failure to do so is undefined behavior. Your code should look something like this:
va_list args;
va_start(args, dummy);
...
char *format = va_arg(args, char *);
...
va_list argsCopy;
va_copy(argsCopy, args);
vsprintf(..., argsCopy);
va_end(argsCopy);
...
mystruct *ms = va_arg(args, mystruct *);
...
va_end(args);
The problem is that it's up to the implementation how to implement a va_list -- it might contain all the info and state for extracting arguments directly, or it might contain a pointer to something that holds the state indirectly. So passing it to vsprintf might make a copy of all the relevant state or it might not.
What you want for what you are trying to do is a vspintf-like function that takes a va_list * rather than a va_list, so you can ensure you have the proper state after it returns. Unfortunately, the standard does not provide any such function.