Ответ 1
Короткий ответ: нет, 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
(или что-то другое) для выделения node для каждого аргумента и создания каких-то динамических данных (например, связанный список, динамический массив), чтобы удерживать аргументы, пока вы не будете готовы их использовать.
Вам также придется добавить код, чтобы справиться с освобождением этой памяти, как только вы закончите с определенной связанной функцией. Это также фактически изменило бы вашу сигнатуру функции непосредственно на va_list, чтобы использовать любую структуру данных, которую вы разработали, чтобы хранить список аргументов.
[end edit]
Это означало бы, что подпись для каждой другой функции, которую вы собираетесь вызывать, должна быть:
int function(va_alist args);
... и тогда каждая из этих функций должна была бы получить свои аргументы через va_arg
, поэтому (например) функцию, которая собиралась взять два ints в качестве своих аргументов, и вернуть их сумму будет выглядеть примерно так:
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 вообще - вам просто нужно использовать язык ассемблера, даже начинать.