我想做一些关于va_list的练习。这是我的代码。
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 );
}
如上所示,我写了一个scanf((,它是工作。
现在我想重定向 myscanf(( 中参数的值。
例如,我可以将 fmt 重定向到 myscanf(( 中分配的空间
int myscanf( char* fmt, ... ) {
char newFmt[10] = "%d %d";
va_list ap;
va_start ( ap, fmt );
vfscanf ( stdin, newFmt, ap );
va_end ( ap );
}
但是,当我试图改变其他人参数的值时,我感到困惑。
我可以通过 va_arg(( 获取这些变量参数,但我无法修改它们,因为 va_arg(( 是一个宏。
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 );
}
有什么建议吗?
-----------编辑-----------
感谢您的回复,
但有些事情应该澄清。
在这种情况下,"值"是"地址"。因此,我的目的是更改目标地址,以便 vfscanf(( 将值读取并将值写入另一个地址空间。
例如
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 );
}
当我将 fmt 更改为 newFmt 时,我们希望直接更改 va_list 中的值(在本例中为地址(。
解析问题解决了,因为我可以在解析"%..."从格式字符串。如果解决了上述问题,这些空格地址将重复替换输入。
可变参数函数
要scanf
的参数将始终是指针,而不是示例中的值。获得scanf
论点的正确方法是int *arg1 = va_arg(ap, int*);
- 你不需要投射。
如果你想操纵scanf
的行为方式,你必须首先知道可变参数函数是如何工作的(你可以通过阅读任何va_*
函数系列的手册来获得它(。大多数体系结构中的变量ap
是指向函数堆栈帧的指针。在这种情况下,它指向fmt
之后的下一个变量。
您的示例
对于示例中的scanf
,它将指向指针列表(因为指向scanf
的所有参数都必须是指针(。所以你应该像这样把它放到你的指针中:
int *a = va_arg(ap, int*);
/* Then you can modify it like this: */
*a = 666;
这有一些问题。
完成参数操作后,必须将fmt
和ap
传递给 vfscanf
,然后 将解析fmt
并期望n
元素(格式字符串中的元素数量(。问题是ap
现在只会给我们n - x
元素(x
你在自己的函数中"弹出"的元素数量(。举个小例子:
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.
*/
在这个简单的示例中,您已经可以看到问题所在。 vfscanf
将为它在格式字符串中找到的每个元素调用va_arg
,该字符串n
,但只有n - x
是可弹出的。这意味着未定义的行为 - vfscanf
将编写它不应该写的地方,并且很可能会使程序崩溃。
四处劈砍
为了克服这一点,我建议与va_copy
进行一些工作。va_copy
的签名是:
void va_copy(va_list dest, va_list src);
以及一些需要了解的信息(来自手册(:
每次调用 va_copy(( 必须与同一函数中相应的 va_end(( 调用相匹配。一些不提供va_copy((的系统反而__va_copy,因为这是提案草案中使用的名称。
解决方案:
#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 %dn", a, b);
return 0;
}
结论和警告
您应该注意几件事。首先,如果你把元素的黑客攻击放在调用vfscanf
之前,你设置的值将丢失,因为vfscanf
会覆盖这些位置。
接下来,您还应该注意,这是一个非常具体的用例。我事先知道我要传递两个整数作为参数,所以我在设计myscanf
时考虑到了这一点。但这意味着你需要一个解析传递来找出哪些参数属于哪种类型 - 如果你不这样做,你将再次输入未定义的行为。编写这种解析器非常简单,应该不是问题。
编辑后
在你在你的澄清编辑中所说的之后,我只能提出一个围绕vfscanf()
的小包装函数,因为你不能直接操作va_list
变量。你不能直接写入堆栈(理论上,你不能,但如果你做了一些内联组装,你可以,但这将是一个丑陋的黑客,非常不可移植(。
它将非常丑陋和不可移植的原因是内联程序集必须考虑体系结构如何处理参数传递。编写内联汇编本身已经非常丑陋了......查看有关其内联装配的官方 GCC 手册。
回到你的问题:
- 堆栈溢出:如何填充va_list
这个答案解释了很多,所以我不会在这里再说了。答案的最终结论是**不,你不这样做"。但是,您_can做的是一个包装器。喜欢这个:
#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 %dn", a, b);
printf("Values c d: %d %dnn", c, c);
printf("Type two values: ");
myscanf("%d %d", &a, &b);
printf("nValues a b: %d %dn", a, b);
printf("Values c d: %d %dn", c, d);
return 0;
}
请注意,只能在编译时为可变参数函数构建参数列表。不能有动态更改的参数列表。换句话说,您必须对想要处理的每个案例进行硬编码。如果用户输入不同的内容,您的程序将表现得非常奇怪,并且很可能会崩溃。
唯一的方法是直接传递更新的参数,因为va_list
无法修改。在您的情况下,您应该解析格式字符串以了解va_list
的实际内容,然后将兼容的参数集直接传递给fscanf()
(而不是vfscanf()
(。
不可能直接进行,但您可以执行以下操作。
int myscanf( char* fmt, ... ) {
va_list ap;
va_start ( ap, fmt );
int newA;
scanf("%d",&new);
vfscanf ( stdin, fmt, ap );
va_end ( ap );
}
我认为这将按照您想要的方式做。
在给定的平台上,您可以使用一些棘手的技巧:
-
va_list
基本上是指向某些数据的指针(通常char *
(, -
va_arg
基本上是指针算法和强制转换
因此,您可以为 int 分配一个包含两个指针的数组,设置值并使用它调用 vfscanf。像这样:
int *hack[2];
hack[0] = &gC;
hack[1] = &gD;
vscanf(stdin, fmt, (va_list)hack);
请注意,这是高度不可移植的,非常棘手且容易出错。即使它基本上在许多平台上工作,也存在很多问题。