【问题标题】:convert va_list to fixed arguments将 va_list 转换为固定参数
【发布时间】:2011-04-09 10:01:09
【问题描述】:

我正在编写原型的线程生成函数:

void Thread_create(void (*func)(void*), int argc, ...);

我已经传入了参数计数,所以确定长度没有问题。 问题是如何将 func 重新转换为具有 argc 长度的函数,然后使用我拥有的参数调用它?

编辑:我还限制函数只接受 void* 参数(即无需担心传入任何其他类型)

例如:

void foo(void *bar, void *baz);
void fooTwo(void *bar, void *baz, void *bam);
int main(int argc, char *argv[]){
    Thread_create(&foo, 2, (void*)argv[0], (void*)argv[1]); //foo gets called in a new thread with the arguments: argv[0] and argv[1]
    Thread_create(&fooTwo, 3, (void*)argv[0], (void*)argv[1], (void*)argv[2]); //fooTwo gets called in a new thread with the arguments: argv[0] and argv[1] and argv[2]
    return 0;
}

旁注:形式的解决方案

Thread_create(void (*func)(void*), int argc, ...); //call with 1 arg
Thread_create(void (*func)(void*, void*), int argc, ...); //call with 2 args
Thread_create(void (*func)(void*, void*, void*), int argc, ...); //call with 3 args

不起作用,因为我无法通过线程创建库调用传递该信息,无论它可能是 pthread_create 还是 windows ThreadCreate 函数。

【问题讨论】:

  • 我不太明白。你是说你有一个指向一个函数的指针,这个函数需要固定数量的参数?你能举例说明你想做什么吗?
  • 另外,您能否决定您对 C 还是 C++ 感兴趣?解决方案可能会大不相同。

标签: c variadic-functions


【解决方案1】:

好吧,除非您了解相关平台的 CPU 架构和 ABI 并自己编写代码,否则您无法通过编程方式自行构建堆栈框架。

例如,对于 x86 32 位的通用调用约定,您需要以相反的顺序将参数传递给堆栈(例如,最后一个参数在前),然后通过 call 调用函数,一旦返回,通过弹出清理堆栈值(或通过调整堆栈指针)。

所以对于函数“foo(int bar, int baz)”,你会:

pushl <value-for-baz>
pushl <value-for-bar>
call  foo
addl  8, $esp

也许您也可以在 C 中编写代码,但是从 C 中弄乱堆栈肯定需要一些汇编魔法。

此外,当使用不同的编译器标志时,堆栈帧甚至可能看起来不同。 x86_64 将参数先放入寄存器,只有当参数多于寄存器时才使用堆栈。

简而言之,不要那样做。 :)

唯一的选择是创建一个大的条件,其中每个分支将调用具有所需数量的参数的正确函数,或您建议的重载函数,或使用数据结构来传递此信息,如其他答案。

Thread_create(void (*func)(void), int argc, ...)
{
  if (argc == 1)
     ((void (*)(void *)) func)(arg0);
  else if (argc == 2)
     ((void (*)(void *, void *)) func)(arg0, arg1); 
}

【讨论】:

  • 是的,我只是希望可能有一些库包含程序集的所有变体。
  • 再三考虑,你说的话听起来不正确。必须有一个统一的方法来调用函数,而不是简单地将参数推入寄存器。原因是如果有一个指针被传递给你的第二个线程创建示例怎么办?那将必须正确调用该函数。由于编译器无法提前确定正在调用的函数,因此所有函数都必须遵循约定。我错过了什么吗?
  • @chacham:所有函数都可以遵循将前 N 个参数放入寄存器的约定。
  • 当你通过一个指针调用一个函数时,你总是告诉编译器它是什么类型的函数:要么通过指针的原始类型,要么通过强制转换。在示例中,我使用了显式转换,并首先将相同的函数指针转换为具有单个参数,然后再转换为两个。这样 C 编译器就知道如何传递参数。给定函数调用的外观是 CPU 和 ABI 规范/调用约定的属性。所以一旦编译器知道了类型,它就可以调用它,不管指针指向哪里。
  • 对,但这意味着所有相同形式的函数,例如“void foo(void*, void*)”,必须以相同的方式传递参数。编译器无法决定它希望某些与该格式匹配的函数的参数通过寄存器传递,而其他函数通过堆栈传递。
【解决方案2】:

您要做的不是变量参数的用途。最好只使用一个附加的void* 参数,该参数将传递回用户函数。这是实现带有用户数据的回调的常用方式。

typedef void (*ThreadMainFunc)(void*);

void ThreadCreate(ThreadMainFunc func, void* user_data){
  // do whatever you need to do
  func(user_data);
  // whatever else
}

struct ThreadData{
  // arguments here as member
  int arg1;
  double arg2;
};

void MyThreadMain(void* my_data){
  ThreadData* my_real_data =(ThreadData*)my_data;
  // use my_real_data here
}

int main(){
  ThreadData data;
  data.arg1 = 42;
  data.arg2 = 13.37;
  CreateThread(&MyThreadMain,&data);
//                           ^^^^^ --- you don't need to cast to void, only from void
}

【讨论】:

  • 问题是我无法更改函数原型,这是一个库调用
  • @chacham15: 哪个函数原型? ThreadMainFunc的那个?
  • 被调用的函数。 IE。 “void foo(void *bar, void *baz);”是库的一部分。是的,我可以创建一个包装它的函数,但是对库的每个函数调用都这样做是浪费时间。
  • 它位于代码的 //use my_real_data here 部分。本质上,您包装了函数。但我想在一个新线程中调用 1000 个不同函数中的任何一个。看到问题了吗?
  • 如果您不想为所有可能的架构在汇编中编写调用站点,恐怕这是不可能的。 ://
猜你喜欢
  • 2014-09-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多