Короткий ответ: нет, C не поддерживает это.
Вы могли бы собрать вместе внешний интерфейс, который взял адрес функции и создал фрейм стека для вызова этой функции с этими аргументами, но он не был бы переносимым 1 . Если вы хотите сделать его переносимым, и могут свободно изменять другие функции, которые вы собираетесь вызывать через этот интерфейс (в форму, которая не совсем подходит для прямого вызова), вы можете переписать их все получить va_alist
в качестве единственного параметра и получить правильный номер / тип параметров, используя va_arg
:
// pointer to function taking a va_alist and return an int:
typedef int (*func)(va_alist);
void invoke(func, ...) {
va_alist args;
va_start(args, func);
func(args);
va_end(args);
}
Редактировать: Извините, как указал @missingno, я не сделал эту работу так, как планировалось. В действительности это должны быть две функции: одна принимает входные данные и упаковывает их в структуру, а другая - принимает структуру и вызывает предполагаемую функцию.
struct saved_invocation {
func f;
argument_list args;
};
saved_invocation save(func f, ...) {
saved_invocation s;
va_alist args;
s.f = f;
va_start(args, f);
va_make_copy(s.args, args);
va_end(args);
}
int invoke(saved_invocation const *s) {
s->f(s->args);
}
Для va_make_copy
вы попадаете на более нестандартные вещи. Это не то же самое, что va_copy
- va_copy
, как правило, просто хранит указатель на начало аргументов, который остается действительным, пока текущая функция не вернется. Для va_make_copy
вам нужно будет сохранить все фактические аргументы, чтобы вы могли получить их позже. Предполагая, что вы использовали структуру argument
, которую я обрисовал ниже, вы будете проходить через аргументы, используя va_arg
, и использовать malloc
(или любой другой), чтобы выделить узел для каждого аргумента, и создать некую динамическую структуру данных ( например, связанный список, динамический массив) для хранения аргументов, пока вы не будете готовы их использовать.
Вы также должны были бы добавить некоторый код для освобождения этой памяти, как только вы закончили с определенной связанной функцией. Это также фактически изменило бы вашу сигнатуру функции с непосредственного получения va_list на любую структуру данных, которую вы разработали для хранения списка аргументов.
[конец редактирования]
Это будет означать, что подпись для каждой другой функции, которую вы собираетесь вызывать, должна быть:
int function(va_alist args);
... и тогда каждая из этих функций должна будет получить свои аргументы через va_arg
, поэтому (например) функция, которая собиралась бы взять два целых числа в качестве своих аргументов, и вернуть их сумму, будет выглядеть примерно так :
int adder(va_alist args) {
int arg1 = va_arg(args, int);
int arg2 = va_arg(args, int);
return arg1 + arg2;
}
У этого есть две очевидные проблемы: во-первых, даже несмотря на то, что нам больше не нужна отдельная оболочка для каждой функции, мы все равно заканчиваем тем, что добавляем дополнительный код в каждую функцию, чтобы позволить ее вызывать через одну оболочку. С точки зрения размера кода это вряд ли будет намного лучше, чем безубыточность, и может легко привести к чистым убыткам.
Однако гораздо хуже, поскольку теперь мы извлекаем все аргументы для всех функций в виде списка переменных переменных, мы больше не получаем проверку типов для аргументов. Если бы мы хотели этого достаточно, можно было бы (конечно) добавить небольшой тип обертки и код, чтобы справиться и с этим:
struct argument {
enum type {CHAR, SHORT, INT, LONG, UCHAR, USHORT, UINT, ULONG, /* ... */ };
union data {
char char_data;
short short_data;
int int_data;
long long_data;
/* ... */
}
}
Тогда, конечно, вы бы написали еще больше кода, чтобы проверить, что перечисление для каждого аргумента указывало, что это был ожидаемый тип, и извлекать правильные данные из объединения, если оно было. Это, однако, добавило бы серьезного уродства к вызову функций - вместо:
invoke(func, arg1, arg2);
... вы получите что-то вроде:
invoke(func, make_int_arg(arg1), make_long_arg(arg2));
Очевидно, что можно сделать. К сожалению, это все еще не приносит пользы - первоначальная цель сокращения кода почти наверняка была потеряна. Это исключает функцию-обертку - но вместо простой функции с простой оберткой и простым вызовом мы получаем сложную функцию и сложный вызов и небольшую гору дополнительного кода для преобразования значения в наш специальный тип аргумента, и другой для получения значения из аргумента.
Хотя есть случаи, которые могут оправдать подобный код (например, написание интерпретатора для чего-то вроде Lisp), я думаю, что в этом случае это приводит к чистым убыткам.Даже если мы игнорируем дополнительный код для добавления / использования проверки типов, код в каждой функции для получения аргументов вместо их получения напрямую работает не только над тем оберткой, которую мы пытаемся заменить.
- «Не будет портативным» - это почти преуменьшение - вы действительно не можете сделать это в C - вам просто нужно использовать язык ассемблера, чтобы даже начать.