Переменные аргументы в C, как получить значения с общим типом?

Я пытаюсь использовать C stdarg.h lib с общим типом. Тип int, мой общий тип > , чтобы понять его, пожалуйста, держите чтение. Итак, моя проблема:

У меня есть функция, которая принимает переменное количество аргументов. как

void function (int paramN, ...);

В моей программе нет способа узнать, что является типом переменных аргументов, это может быть char, массив, int, short, function point и т.д.... например

function (paramN, "Hey, I'm a string", 1, function_pint, array, -1); // -1 is a sentinel.

Итак, я думаю, что int, 32 бит, в системе x86 (32 бита), это будет содержать весь адрес памяти. Итак, если я получу все аргументы с помощью int, это не будет проблемой, например, "Эй, я строка", адрес этой строки, подходящий обычно в переменной 32 бит, поэтому мне нужно только сделать в ролях.

Я прав?
Могу ли я это сделать?
Примечание. Я не хочу, чтобы моя функция, как printf (это решение, не подходит в этом случае нормально?)

Спасибо за ответ.
И извините за мой плохой английский.

Ответ 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" ).

Ответ 2

Используйте void * (или типизированную структуру) для каждого параметра и используйте структуру с аргументом типа (целое число). Указатель/объединение содержит фактическое значение.

Другими словами, каждый параметр передается с указателем на типизированную структуру. Каждый экземпляр этой типизированной структуры содержит значение. Тип этого "значения" содержится в этой типизированной структуре.

Лучший пример:

typedef struct  {
  int type;
  union {
    int int_value;
    double double_value;
    ...
  };
} Param;

void function(Param *p1, Param *p2, ...)

Последний пример такого трюка, с которым я столкнулся, был DBus.

Ответ 3

Вы не можете сделать это с переменными аргументами так, как вы описываете, потому что никакая информация о типе передаваемых вами аргументов не сохраняется после компиляции, если вы не сделаете это явно. Даже прохождение адреса переменной аргумента не скажет вам об этом, потому что биты в памяти, представляющие строку, могут представлять число или что-то еще.

Чтобы заставить varargs работать с типами переменных, вы можете хранить информацию о типе в самих аргументах (например, как описано jldupont в его ответе), или вы можете хранить информацию в аргументе без переменной (например, в строке формата например printf).

Ответ 4

Преодоление из http://en.wikipedia.org/wiki/Stdarg.h:

Для определения типов [...] неименованных аргументов, переданных функции, не существует механизма. Функция просто требуется знать или определять это как-то, средства которой различаются.

Это ваша функция не может знать только аргументы, аргументы которых являются строками. Вам нужно будет сказать свою функцию, что ожидать как-то. Вот почему соглашение printf настолько распространено.

Ответ 5

Попробуйте u? intptr_t, который определен в stdint.h. Это целое число, которое должно быть достаточно большим, чтобы удерживать указатель.

Вы также можете подумать о том, что происходит, когда вы передаете число с плавающей запятой; он преобразуется и переходит в двойное. Если ваша программа ожидает int, это разрушит ее представление о стеке. Уч. И компилятор не может поймать это.

И ваша функция, как определено, имеет (запрет любого битового кодирования в paramN, в этом случае он должен быть перечислением или битовым полем или, по крайней мере, без знака), не знает, какой тип параметров он получает, и, как таковой, вряд ли сможет сделать с ними что-нибудь полезное. C не имеет информации о типе времени выполнения на своих объектах.

Что именно вы пытаетесь сделать?