Есть ли способ вычислить длину va_list
? Все примеры, в которых я видел число переменных параметров, указаны явно.
Длина va_list при использовании аргументов переменных списка?
Ответ 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);
}
Ответ 5
Используйте _vscprintf для определения длины списка переменных. https://msdn.microsoft.com/en-us/library/w05tbk72.aspx