преобразовать va_list в фиксированные аргументы

#c #variadic-functions

#c #переменные-функции

Вопрос:

Я пишу функцию порождения потока прототипа:

 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(amp;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(amp;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.

Комментарии:

1. Я не совсем понимаю. Вы хотите сказать, что у вас есть указатель на функцию, которая принимает фиксированное количество аргументов? Не могли бы вы привести пример того, что вы хотели бы иметь возможность делать?

2. Кроме того, не могли бы вы решить, интересуетесь ли вы C или C ? Решения потенциально могут быть очень разными.

Ответ №1:

Ну, вы не можете создать фрейм стека самостоятельно, программно, если вы не знаете архитектуру процессора и ABI рассматриваемой платформы и не кодируете ее самостоятельно.

Например, с общими соглашениями о вызовах на 32-разрядной версии x86 вам нужно передать аргументы в стек в обратном порядке (например, сначала последний аргумент), затем вызвать функцию с помощью 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); 
}
  

Комментарии:

1. Да, я просто надеялся, что может существовать какая-нибудь библиотека, которая содержала бы все варианты сборки.

2. При повторном рассмотрении то, что вы говорите, звучит неправильно. Должен существовать единый метод вызова функции, который не просто помещает аргументы в регистры. Причина в том, что что, если бы был передан указатель на ваш второй пример создания потока? Это должно было бы вызвать функцию правильно. Поскольку компилятор не может заранее определить, какая функция вызывается, все функции должны следовать соглашению. Я что-то упускаю?

3. @chacham: Все функции могут следовать соглашению о размещении первых N аргументов в регистрах.

4. Когда вы вызываете функцию через указатель, вы всегда сообщаете компилятору, что это за функция: либо через исходный тип указателя, либо через приведение. В приведенном примере я использовал явное приведение и сначала привел один и тот же указатель на функцию, чтобы иметь один аргумент, а затем два из них. Таким образом, компилятор C знает, как передавать аргументы. То, как выглядит вызов данной функции, зависит от свойств процессора и спецификации / соглашения о вызове ABI. Таким образом, как только компилятор узнает тип, он может вызвать его, независимо от того, куда указывает указатель.

5. Верно, но это означает, что все функции одной и той же формы, например, «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(amp;MyThreadMain,amp;data);
//                           ^^^^^ --- you don't need to cast to void, only from void
}
  

Комментарии:

1. Проблема в том, что я не могу изменить прототип функции, это вызов библиотеки

2. @chacham15: Прототип какой функции? Что из ThreadMainFunc ?

3. Вызываемой функции. Т.е. «void foo(void *bar, void *baz); » является частью библиотеки. Да, я мог бы создать функцию, которая оборачивает это, но делать это для каждого вызова функции в библиотеке — утомительная трата времени.

4. Это в части вашего кода //use my_real_data here. По сути, вы завернули функцию. Но я хочу вызвать любую из 1000 различных функций в новом потоке. Видите проблему?

5. Боюсь, это невозможно, если вы не хотите кодировать вызывающий сайт в ассемблере для каждой возможной архитектуры. :/