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:
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.
va_arg(ap, int); // get the value of &a in main()
- This is incorrect all by itself, since the type of the argument isint*
, notint
. – Vantage