Ответ 1
Вы не можете сделать это так, как вы его описываете.
Соглашение о вызове C для вызывающего объекта для размещения аргументов в стеке, но не содержит никакой информации о типах, поэтому вызывающий должен иметь способ его найти (по крайней мере, размер переменных).
-
Нет проблем для функций с прототипами, которые известны каждому типу.
-
С функциями с переменным числом или параметрами (variadic) это более сложно, вам нужно вызвать va_arg для каждого аргумента, чтобы прочитать каждую переменную, и вы должны указать тип для va_arg. Если вы предоставляете тип, который не является реальным, компилятор не будет жаловаться (это не может быть информация о времени выполнения), но все может произойти (как правило, что-то плохое).
Следовательно, вы должны передавать тип.
В некоторых случаях вы можете предсказать тип (например: счетчик, за которым следуют некоторые int и т.д.), но обычно вы передаете его закодированным в параметрах. Вы можете передать его закодированным в строку формата, например printf, трюк объединения, описанный jldupont, также достаточно распространен.
Но в любом случае вам нужно пройти его.
Вы действительно не можете полагаться на базовое двоичное представление данных, даже не на размер данных. Даже если программа, похоже, работает, когда вы ее пишете, она не имеет совместимости и может сломаться с любым изменением системы, компилятора или даже при изменении параметров компиляции.
Отпустим пример, в котором вы передаете тип, за которым следует аргумент (таким образом, ни трюк объединения, ни строка формата, как printf). То, что он делает, - это преобразовать все, что передало значение, чтобы удвоить и добавить их, но действительно не полезно:
#include <stdio.h>
#include <stdarg.h>
enum mytypes {LONG, INT, FLOAT, DOUBLE };
double myfunc(int count, ...){
long tmp_l;
int tmp_i;
double tmp_d;
double res = 0;
int i;
va_list ap;
va_start(ap, count);
for(i=0 ; i < count; i++){
int type = va_arg(ap, enum mytypes);
switch (type){
case LONG:
tmp_l = va_arg(ap, long);
res += tmp_l;
break;
case INT:
tmp_i = va_arg(ap, int);
res += tmp_i;
break;
case FLOAT:
/* float is automatically promoted to double when passed to va_arg */
case DOUBLE:
tmp_d = va_arg(ap, double);
res += tmp_d;
break;
default: /* unknown type */
break;
}
}
va_end(ap);
return res;
}
int main(){
double res;
res = myfunc(5,
LONG, (long)1,
INT, (int)10,
DOUBLE, (double)2.5,
DOUBLE, (double)0.1,
FLOAT, (float)0.3);
printf("res = %f\n", res);
}
В этом примере используется новый stdarg-вариационный заголовок, определенный на C99. Чтобы использовать его, вам нужно иметь хотя бы один фиксированный параметр для вашей функции (в этом примере count
). Хорошо, если, таким образом, вы можете иметь несколько вариационных списков в своей функции (например, что-то вроде myfunc(int count1, ..., int count2, ...)
). Плохо то, что вы не можете иметь чисто вариационную функцию (например, что-то вроде myfunc (...) как в старом формате. Вы все равно можете использовать старый формат с использованием заголовков совместимости с varargs, но это сложнее и редко необходимо, потому что вам нужны типы, но также способ узнать, что список закончен, и что-то вроде count удобно (но не единственный способ сделать это, например, "terminator" ).