【问题标题】:How to replace values in va_list?如何替换 va_list 中的值?
【发布时间】:2016-07-22 11:57:21
【问题描述】:

我想做一些关于 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 中的值(在本例中为地址)。

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

【问题讨论】:

  • va_arg(ap, int); // get the value of &amp;a in main() - 这本身就是不正确的,因为参数的类型是int*,而不是int
  • 在 c++ 中,使用可变参数模板而不是省略号。
  • 我将删除 C++ 标签,因为带有省略号的可变参数与现代 C++ 没有密切关系。如果你真的想用 C++ 做到这一点,我会接受 @Jarod42 的建议。
  • @hwliu 那么,您不想在 gA 和 gB 中分配任何内容吗?通话结束后,他们会保留以前的任何价值观吗?
  • @Enzo Ferber 是的,vfscanf() 不应访问 gA 和 gB。我很抱歉我的表达不好。

标签: c variable-length


【解决方案1】:

可变函数

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.
 */

在这个简单的示例中,您已经可以看到问题所在。 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 %d\n", a, b);
    return 0;
}


结论和警告

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

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


修改后

在你在澄清编辑中所说的之后,我只能在vfscanf() 周围提出一个小包装函数,因为你不能直接操作va_list 变量。你不能直接写入堆栈(理论上你不能,但如果你做了一些内联汇编你可以,但这将是一个丑陋的黑客并且非常不便携)。

它会非常丑陋和不可移植的原因是内联汇编必须考虑架构如何处理参数传递。自己编写 inline-assembly 已经很丑陋了...查看this 了解有关其内联汇编的官方 GCC 手册。

回到你的问题:

这个答案解释了很多,所以我不再在这里说了。答案的最终结论是**不,你不这样做”。但是你_可以做的是一个包装器。像这样:

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

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

【讨论】:

  • 我会给这个答案+2。
  • 感谢您的回复,但有些事情需要澄清。我已经编辑了我的主题。
  • @hwliu 好的,检查一下。我编辑以回答您的澄清编辑。希望它现在可以解决您的问题。
  • 感谢您的提醒。当我将汇编代码内联到这段代码中时,我会考虑可移植性问题。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

    我认为这会如你所愿。

    【讨论】:

    • 谢谢,但是这种方法会使程序的行为与最初的目的不同。 (程序会等待输入2次)
    • 这似乎是明智的。 @hwliu 程序可能会在getcharscanf 内等待更多次输入
    【解决方案4】:

    在给定的平台上,您可能会使用一些棘手的技巧:

    1. va_list 基本上是指向某些数据的指针(通常是char *),
    2. va_arg 基本上是指针算术和强制转换

    因此,您可以分配一个包含两个指向 int 的指针的数组,设置值并使用它调用 vfscanf。比如:

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

    注意这是高度不可移植的,非常棘手且容易出错。这样有很多问题,即使它基本上可以在许多平台上运行。

    【讨论】:

      猜你喜欢
      • 2017-05-10
      • 2018-02-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-04
      • 2015-11-14
      • 2014-02-01
      相关资源
      最近更新 更多