如何替换va_list中的值?

hwl*_*liu 7 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 );
}
Run Code Online (Sandbox Code Playgroud)

如上所示,我写了一个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 );
}
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试更改其他参数的值时,我感到困惑.

我可以通过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 );
}
Run Code Online (Sandbox Code Playgroud)

有什么建议吗?

- - - - - -编辑 - - - - - -

谢谢你的回复,

但应澄清一些事情.

在这种情况下,"价值"是"地址".因此,我的目的是更改目标地址,以便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 );
}
Run Code Online (Sandbox Code Playgroud)

当我将fmt更改为newFmt时,我们希望直接在va_list中更改值(在本例中为地址).

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

Enz*_*ber 6

变异函数

参数scanf将始终是指针,而不是示例中的值.得到论证的正确方法scanfint *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;
Run Code Online (Sandbox Code Playgroud)

这有一些问题.

当你完成操作的参数,你必须通过fmtapvfscanf,这将然后解析fmt,并期望n(格式字符串元素的量)的元素.问题是ap现在只会给我们n - x元素(x你自己的函数中"poped"的元素数量).一个小例子:

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.
 */
Run Code Online (Sandbox Code Playgroud)

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


哈哈克

为了克服这个问题,我提出了一些工作va_copy.签名va_copy是:

void va_copy(va_list dest, va_list src);
Run Code Online (Sandbox Code Playgroud)

还有一些需要了解的内容(来自手册):

每次调用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;
}
Run Code Online (Sandbox Code Playgroud)


结论和警告

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

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


编辑完成后

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

它将是非常丑陋和不可移植的原因是内联汇编必须考虑架构如何处理参数传递.编写内联汇编本身已经非常难看......请查看此内容以获取其内联汇编的官方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;
}
Run Code Online (Sandbox Code Playgroud)

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


Ser*_*gio 1

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