如何替换va_list中的值?

时间:2016-07-22 11:57:21

标签: c variable-length

我想对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中更改值(在本例中为地址)。

解析问题已解决,因为我可以在从格式字符串解析“%...”时动态分配空间。如果上述问题得到解决,这些空间地址将重复替换输入。

4 个答案:

答案 0 :(得分:6)

Variadic Functions

scanf的参数将始终是指针,而不是示例中的值。获得scanf参数的正确方法是int *arg1 = va_arg(ap, int*); - 而且您不需要施放。

如果您想要操纵scanf行为的方式,您必须先了解variadic functions的工作原理(您可以通过阅读任何va_*系列函数的手册来了解它)。大多数体系结构中的变量ap是指向函数堆栈帧的指针。在这种情况下,它指向fmt之后的下一个变量。

<小时/>

您的示例

在您的示例中为scanf的情况下,它将指向一个指针列表(因为 scanf的所有参数必须是指针)。所以你应该把它放到你的指针中:

int *a = va_arg(ap, int*);

/* Then you can modify it like this: */
*a = 666;

这有一些问题。

当您完成对参数的操作时,您必须将fmtap传递给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. */ 是您在自己的函数中“poped”的元素数量。一个小例子:

vfscanf

在这个简单的例子中,您已经可以看到问题所在。对于在格式字符串中找到的每个元素,va_arg会调用n ,即n - x,但只有vfscanf可以弹出。这意味着未定义的行为 - va_copy将写入不应该的地方,并且很可能会使您的程序崩溃。

<小时/>

Hack Around

为了解决这个问题,我建议用va_copy做一些工作。 void va_copy(va_list dest, va_list src); 的签名是:

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

要了解它(来自手册):

  

va_copy()的每次调用必须与同一函数中相应的 va_end()调用相匹配。某些不提供 va_copy()的系统会改为使用 __ va_copy ,因为这是提案草稿中使用的名称。

解决方案:

vfscanf

<小时/>

结论和警告

你应该注意几件事。首先,如果您在调用vfscanf之前放置黑客攻击,则您设置的值将会丢失,因为myscanf会覆盖这些位置。

接下来,您还应该注意这是一个非常具体的用例。我事先知道我将传递两个整数作为参数,所以我设计了vfscanf()时考虑到了这一点。但这意味着你需要一个解析传递来找出哪种类型的参数 - 如果你不这样做,你将再次输入未定义的行为。编写这种解析器是非常直接的,不应该是一个问题。

<小时/>

编辑后

在您的澄清编辑中所说的之后,我只能在va_list周围提出一个小的包装函数,因为您无法直接操作#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; } 个变量。你不能直接写入堆栈(理论上,你不能,但如果你做了一些内联汇编,你可以,但那将是一个丑陋的黑客,非常不便携)。

它将是非常丑陋和不可移植的原因是内联汇编必须考虑架构如何处理参数传递。编写内联汇编本身已经非常难看......请查看this关于其内联汇编的官方GCC手册。

回到你的问题:

那个答案解释了很多,所以我不会在这里再说一遍。答案的最终结论是**不,你不这样做“。你可以做什么,但是,这是一个包装。就像这样:

CheckBox

请注意,您只能在编译时为变量函数构建参数列表。您不能拥有动态更改的参数列表。换句话说,你必须对你想要处理的每个案例进行硬编码。如果用户输入不同的内容,您的程序将表现得非常奇怪并且很可能会崩溃。

答案 1 :(得分:1)

这是不可能的,但你可以这样做。

int myscanf( char* fmt, ... ) {
  va_list ap;
  va_start ( ap, fmt );
  int newA;
  scanf("%d",&new);
  vfscanf ( stdin, fmt, ap );
  va_end ( ap );
}

我认为这会做你想做的事情。

答案 2 :(得分:1)

在给定的平台上,您可以使用一些棘手的黑客:

  1. va_list基本上是指向某些数据的指针(通常为char *),
  2. va_arg基本上是指针算术和强制转换
  3. 因此,您可以为int分配两个指针的数组,设置值并使用它调用vfscanf。类似的东西:

    int *hack[2];
    hack[0] = &gC;
    hack[1] = &gD;
    vscanf(stdin, fmt, (va_list)hack);
    

    BEWARE 这非常不便携,非常棘手且容易出错。即使它基本上可以在许多平台上运行,但是存在很多问题。

答案 3 :(得分:0)

唯一的方法是直接传递更新的参数,因为va_list无法修改。在您的情况下,您应该解析格式字符串以了解va_list的实际内容,然后将兼容的参数集直接传递给fscanf()(而不是vfscanf())。