Подтвердить что ты не робот

Длина va_list при использовании аргументов переменных списка?

Есть ли способ вычислить длину va_list? Все примеры, в которых я видел число переменных параметров, указаны явно.

4b9b3361

Ответ 1

Нет способа вычислить длину va_list, поэтому вам нужна строка формата в printf как функции.

Макросы <, доступные для работы с va_list::

  • va_start - начните использовать va_list
  • va_arg - получить следующий аргумент
  • va_end - прекратите использование va_list
  • va_copy (начиная с С++ 11 и C99) - скопируйте va_list

Обратите внимание, что вам нужно вызвать va_start и va_end в той же области, что означает, что вы не можете обернуть его в класс утилиты, который вызывает va_start в своем конструкторе и va_end в своем деструкторе (I был укушен этим однажды).

Например, этот класс бесполезен:

class arg_list {
    va_list vl;
public:
    arg_list(const int& n) { va_start(vl, n); }
    ~arg_list() { va_end(vl); }
    int arg() {
        return static_cast<int>(va_arg(vl, int);
    }
};

GCC выводит следующую ошибку

t.cpp: В конструкторе arg_list::arg_list(const int&):
Строка 7: ошибка: va_start используется в функции с фиксированными аргументами
компиляция завершена из-за -Wfatal-errors.

Ответ 2

Один из подходов, который еще не упоминался, заключается в использовании макропроцессора предварительного процессора для вызова функции variadict с использованием длины va_list в качестве первого параметра, а также вперед по аргументам. Это несколько "симпатичное" решение, но не требует ручного ввода длины списка аргументов.

Предположим, что у вас есть следующая функция:

int Min(int count, ...) {
    va_list args;
    va_start(args, count);

    int min = va_arg(args, int);
    for (int i = 0; i < count-1; ++i) {
      int next = va_arg(args, int);
      min = min < next ? min : next;
    }
    va_end(args);

    return min;
}

Идея состоит в том, что у вас есть макрос препроцессора, способный подсчитывать количество аргументов, используя маску для __VA_ARGS__. Существует несколько хороших препроцессорных библиотек для определения длины __VA_ARGS__, включая P99 и Boost Preprocessor, но так, чтобы я не оставил отверстия в этом ответе, вот как это можно сделать:

#define IS_MSVC _MSC_VER && !__INTEL_COMPILER

/**
 * Define the macros to determine variadic argument lengths up to 20 arguments. The MSVC 
 * preprocessor handles variadic arguments a bit differently than the GNU preprocessor,
 * so we account for that here. 
 */
#if IS_MSVC
  #define MSVC_HACK(FUNC, ARGS) FUNC ARGS
  #define APPLY(FUNC, ...) MSVC_HACK(FUNC, (__VA_ARGS__))
  #define VA_LENGTH(...) APPLY(VA_LENGTH_, 0, ## __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#else
  #define VA_LENGTH(...) VA_LENGTH_(0, ## __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#endif

/**
 * Strip the processed arguments to a length variable.
 */
#define VA_LENGTH_(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, N, ...) N

Примечание. Многие из вышеперечисленных шумов поддерживают поддержку MSVC.

С помощью описанного выше вы можете создать один макрос для выполнения всех операций на основе длины:

/**
 * Use the VA_LENGTH macro to determine the length of the variadict args to
 * pass in as the first parameter, and forward along the arguments after that.
 */
#define ExecVF(Func, ...) Func(VA_LENGTH(__VA_ARGS__), __VA_ARGS__)

Этот макрос способен вызывать любую переменную функцию, если она начинается с параметра int count. Короче говоря, вместо использования:

int result = Min(5, 1, 2, 3, 4, 5);

Вы можете использовать:

int result = ExecVF(Min, 1, 2, 3, 4, 5);

Здесь шаблонная версия Min, которая использует тот же подход: https://gist.github.com/mbolt35/4e60da5aaec94dcd39ca

Ответ 3

Нет прямого пути для вариационной функции, чтобы определить, сколько аргументов было передано. (По крайней мере, нет портативного способа, интерфейс <stdarg.h> не предоставляет эту информацию.)

Существует несколько косвенных способов.

Два наиболее распространенных:

  • Строка формата (которая указывает, через какой вы могли бы назвать небольшой простой язык, число и тип остальных аргументов). Этим механизмом используются семейства функций *printf() и *scanf().
  • Значение дозорного, обозначающее конец аргументов. Некоторые из семейств функций Unix/POSIX exec*() делают это, используя нулевой указатель для обозначения конца аргументов.

Но есть и другие возможности:

  • Более просто, целочисленный счетчик, определяющий количество следующих аргументов; предположительно, в этом случае все они будут одного типа.
  • Альтернативные аргументы, где аргумент может быть значением перечисления, определяющим тип следующего аргумента. Гипотетический пример может выглядеть так:
    func(ARG_INT, 42, ARG_STRING, "foo", ARG_DOUBLE, 1.25, ARG_END);
    или даже:
    func("-i", 42, "-s", "foo", "-d", 1.25, "");
    если вы хотите эмулировать методы, как правило, передаются аргументы командам Unix.

Вы даже можете присвоить значение глобальной переменной, чтобы указать количество аргументов:

func_arg_count = 3;
func(1, 2, 3);

который был бы уродливым, но вполне законным.

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

Обратите внимание, что переменная не требуется для обработки всех переданных ей аргументов. Например, это:

printf("%d\n", 10, 20);

напечатает 10 и спокойно проигнорирует 20. Редко есть какая-то причина для использования этой функции.

Ответ 4

Вы можете попробовать использовать функцию _vscprintf, если вы работаете в MS Visual Studio. Вот пример использования _vscprintf, я использовал его, чтобы узнать, сколько места мне требуется для malloc для заголовка консоли.

int SetTitle(const char *format,...){
    char *string;
    va_list arguments;

    va_start(arguments,format);
        string=(char *)malloc(sizeof(char)*(_vscprintf(format,arguments)+1));
        if(string==NULL)
            SetConsoleTitle("Untitled");
        else
            vsprintf(string,format,arguments);
    va_end(arguments);

    if(string==NULL)
        return SETTITLE_MALLOCFAILED;
    SetConsoleTitle(string);
    free(string);
    return 0;
}

Или вы можете сделать это, добавить вывод во временный файл и затем прочитать данные из него в выделенную память, как я сделал в следующем примере:

void r_text(const char *format, ...){
    FILE *tmp = tmpfile();
    va_list vl;
    int len;
    char *str;

    va_start(vl, format);
        len = vfprintf(tmp, format, vl);
    va_end(vl);
    rewind(tmp);
    str = (char *) malloc(sizeof(char) * len +1);
    fgets(str, len+1, tmp);
    printf("%s",str);
    free(str);
    fclose(tmp);
}