将参数动态传递给可变参数函数

发布于 2024-08-10 12:23:27 字数 264 浏览 3 评论 0原文

我想知道是否有任何方法可以动态地将参数传递给可变参数函数。即,如果我有一个函数

int some_function (int a, int b, ...){/*blah*/}

并且我接受来自用户的一堆值,我想要某种方式将这些值传递到函数中:

some_function (a,b, val1,val2,...,valn)

我不想编写所有这些函数的不同版本,但我怀疑没有其他选择?

I was wondering if there was any way to pass parameters dynamically to variadic functions. i.e. If I have a function

int some_function (int a, int b, ...){/*blah*/}

and I am accepting a bunch of values from the user, I want some way of passing those values into the function:

some_function (a,b, val1,val2,...,valn)

I don't want to write different versions of all these functions, but I suspect there is no other option?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

猫弦 2024-08-17 12:23:27

可变参数函数使用调用约定,其中调用者负责从堆栈中弹出函数参数,所以是的,可以动态地执行此操作。它在 C 中没有标准化,通常需要一些汇编来手动推送所需的参数,并正确调用可变参数函数。

cdecl 调用约定要求以正确的顺序推送参数,并且在调用之后,在调用之前作为参数推送的字节将被弹出。通过这种方式,被调用的函数可以接收任意数量的参数,因为调用者将处理将堆栈指针恢复到其调用前的状态。 ... 之前的参数占用的空间是推送字节数的安全下限。其他可变参数在运行时解释。

FFCALL 是一个库,它提供用于将参数动态传递给可变参数函数的包装器。您感兴趣的函数组是 avcall。以下是调用上面提供的函数的示例:

#include <avcall.h>

av_alist argList;
int retVal;
av_start_int(argList, some_function, retval);
av_int(argList, a);
av_int(argList, b);
av_type(argList, val1);
...
av_type(argList, valn);
av_call(argList);

您可能还会找到此链接 讨论在 C 中围绕可变参数函数生成包装器,以便有兴趣证明为什么这不是标准 C 的一部分。

Variadic functions use a calling convention where the caller is responsible for popping the function parameters from the stack, so yes, it is possible to do this dynamically. It's not standardized in C, and normally would require some assembly to manually push the desired parameters, and invoke the variadic function correctly.

The cdecl calling convention requires that the arguments be pushed in the correct order, and after the call, the bytes pushed as arguments before the call are popped. In this way, the called function can receive an arbitrary number of parameters, as the caller will handle reverting the stack pointer to it's pre-call state. The space occupied by the arguments before the ... is the safe lower bound for number of bytes pushed. Additional variadic arguments are interpreted at runtime.

FFCALL is a library which provides wrappers for passing parameters dynamically to variadic functions. The group of functions you're interested in is avcall. Here's an example calling the functions you gave above:

#include <avcall.h>

av_alist argList;
int retVal;
av_start_int(argList, some_function, retval);
av_int(argList, a);
av_int(argList, b);
av_type(argList, val1);
...
av_type(argList, valn);
av_call(argList);

You might also find this link discussing generating wrappers around variadic functions in C, to be of interest in justifying why this isn't part of standard C.

牵你的手,一向走下去 2024-08-17 12:23:27

标准方法是让每个可变参数函数都带有一个对应的 va_list 函数(如 printf 和 vprintf 中)。可变参数版本只是将 ... 转换为 va_list (使用 stdarg.h 中的宏)并调用其 va_list-takeing 姐妹,这确实实际工作。

A standard approach is to have each variadic function accompanied by a va_list-taking counterpart (as in printf and vprintf). The variadic version just converts ... to a va_list (using macros from stdarg.h) and calls its va_list-taking sister, which does actual work.

治碍 2024-08-17 12:23:27

尝试只传递一个数组,然后使用 vararg 宏可能会很有趣。根据堆栈对齐方式,它可能会起作用(tm)。

这可能不是一个最佳解决方案,我发布它主要是因为我发现这个想法很有趣。
尝试后,这种方法在我的 linux x86 上有效,但在 x86-64 上无效 - 它可能可以改进。此方法将取决于堆栈对齐、结构对齐等。

void varprint(int count, ...)
{
    va_list ap;
    int32_t i;

    va_start(ap, count);
    while(count-- ) {
        i = va_arg(ap, int32_t);
        printf("Argument: %d\n", i);
    }
    va_end(ap); 
}

struct intstack
{
    int32_t pos[99];
};

int main(int argc, char** argv)
{
    struct intstack *args = malloc(sizeof(struct intstack));
    args->pos[0] = 1;
    args->pos[1] = 2;
    args->pos[2] = 3;
    args->pos[3] = 4;
    args->pos[4] = 5;

    varprint(5, *args);
    return 0;
}

It might be interesting to try just passing an array, and then use the vararg macros anyway. Depending on stack alignment, it might Just Work (tm).

This is probably not an optimal solution, I mainly posted it because I found the idea interesting.
After trying it out, this approach worked on my linux x86, but not on x86-64 - it can probably be improved. This method will depend on stack alignment, struct alignment and probably more.

void varprint(int count, ...)
{
    va_list ap;
    int32_t i;

    va_start(ap, count);
    while(count-- ) {
        i = va_arg(ap, int32_t);
        printf("Argument: %d\n", i);
    }
    va_end(ap); 
}

struct intstack
{
    int32_t pos[99];
};

int main(int argc, char** argv)
{
    struct intstack *args = malloc(sizeof(struct intstack));
    args->pos[0] = 1;
    args->pos[1] = 2;
    args->pos[2] = 3;
    args->pos[3] = 4;
    args->pos[4] = 5;

    varprint(5, *args);
    return 0;
}
可爱暴击 2024-08-17 12:23:27

根据您传递的内容,您在这里追求的可能是一个受歧视的工会(如评论中所暗示的)。这将避免对可变参数函数或 void* 数组的需要,并回答“some_function 如何知道你实际传递给它的内容”的问题。您可能有这样的代码:

enum thing_code { INTEGER, DOUBLE, LONG };

struct thing
{
 enum thing_code code;
 union
 {
    int a;
    double b;
    long c;
 };
};

void some_function(size_t n_things, struct thing *things)
{
    /* ... for each thing ... */
    switch(things[i].code)
    {
      case INTEGER:
      /* ... */
    }
}

您可以更进一步,通过将 code 替换为一个或多个指向函数的指针来避免切换,这些函数对每个 thing 执行一些有用的操作>。例如,如果您想要做的只是打印出每件事,您可以这样:

struct thing
{
 void (*print)(struct thing*);
 union
 {
   ...
 };
}

void some_function(size_t n_things, struct thing *things)
{
  /* .. for each thing .. */
  things[i]->print(things[i]);
  /* ... */
}

Depending on what it is you're passing around, it could be a discriminated union you're after here (as hinted at in the comments). That would avoid the need for variadic functions or arrays of void*, and answers the question "how does some_function know what you actually passed it". You might have code something like this:

enum thing_code { INTEGER, DOUBLE, LONG };

struct thing
{
 enum thing_code code;
 union
 {
    int a;
    double b;
    long c;
 };
};

void some_function(size_t n_things, struct thing *things)
{
    /* ... for each thing ... */
    switch(things[i].code)
    {
      case INTEGER:
      /* ... */
    }
}

You can take this a step further and avoid the switch by replacing the code with one or more pointers to functions that do something useful with each thing. For example, if what you wanted to do was to simply print out each thing, you could have this:

struct thing
{
 void (*print)(struct thing*);
 union
 {
   ...
 };
}

void some_function(size_t n_things, struct thing *things)
{
  /* .. for each thing .. */
  things[i]->print(things[i]);
  /* ... */
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文