длина-ва списке, когда через список аргументов переменной?
Есть ли способ вычислить длину va_list
? Во всех примерах, которые я видел, количество переменных параметров задается явно.
6 ответов:
Нет способа вычислить длину
va_list
, поэтому вам нужна строка формата вprintf
подобных функциях.Единственные
функциимакросы, доступные для работы сva_list
являются :
va_start
- Начните использоватьva_list
va_arg
- получить следующий аргументva_end
- прекратите использоватьva_list
va_copy
(начиная с C++11 и C99) - копироватьva_list
Пожалуйста, обратите внимание, что вам нужно позвонить
va_start
иva_end
в та же область, что означает, что вы не можете обернуть его в служебный класс, который вызываетva_start
в своем конструкторе иva_end
в своем деструкторе (я был укушен этим однажды).Например, этот класс бесполезен:
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); } };
T.cpp: в конструкторе
arg_list::arg_list(const int&)
:
Строка 7: Ошибка:va_start
используется в функции с фиксированными args
компиляция прекращена из-за-Wfatal-ошибок.
Один из подходов, который еще не был упомянут, заключается в использовании макроса препроцессора для вызова функции 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__)
Этот макрос способен вызывать любую функцию variadict до тех пор, пока он начинается с параметра
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
Нет прямогоспособа для вариадической функции определить, сколько аргументов было передано. (По крайней мере, нет переносимого способа; интерфейс
<stdarg.h>
не предоставляет эту информацию.)Существует несколькокосвенных способов.
Два наиболее распространенных:
Но есть и другие возможности:
- строка формата (которая определяет, с помощью того, что можно назвать небольшим простым языком, число и тип(ы) оставшихся аргументов). Семейства функций
*printf()
и*scanf()
используют этот механизм.- значение sentinel, обозначающее конец аргументов. Некоторые функции семейства 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
. Редко есть какая-либо причина, чтобы воспользоваться этой функцией.
Можно попробовать использовать функцию
_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); }
Хм, если вы не боитесь неприятного взлома asm, то вы можете использовать соглашение о вызове вашего компилятора. Однако это ограничит ваш код конкретной платформой / компилятором / соглашением о вызовах.
Например, в BDS2006 C++ 32bit x86 Windows app (я буду ссылаться только на эту платформу) аргументы помещаются в стек, затем вызываются, а затем значение указателя стека восстанавливается (по размеру используемого стека) после возврата функции. Вот небольшой пример:
double x; x=min(10.0,20.0,30.0,40.0,50.0);
Вызов переводится так:
Обратите внимание на последнюю инструкцию после вызова.Unit1.cpp.28: x=min(10.0,20.0,30.0,40.0,50.0); 00401B9C 6800004940 push $40490000 00401BA1 6A00 push $00 00401BA3 6800004440 push $40440000 00401BA8 6A00 push $00 00401BAA 6800003E40 push $403e0000 00401BAF 6A00 push $00 00401BB1 6800003440 push $40340000 00401BB6 6A00 push $00 00401BB8 6800002440 push $40240000 00401BBD 6A00 push $00 00401BBF E894FDFFFF call min(double,double,????) 00401BC4 83C428 add esp,$28
$28
- это размер, потребляемый 4 аргументами и одним возвращаемым значением. Таким образом, если вы можете прочитать это значение в своей функции, вы можете точно определить количество аргументов (если их размер известен). Так вот рабочий пример:double min(double x,double ...) // = min(x,y) { int n,dn=sizeof(double); asm { mov eax,esp // store original stack pointer mov esp,ebp // get to the parrent scope stack pointer pop ebx pop ebx // this reads the return address of the call pointing to the first instruction after it which is what we want mov esp,eax // restore stack pointer sub eax,eax; // just eax=0 mov al,[ebx+2] // read lowest BYTE of eax with the $28 from the add esp,$28 mov n,eax // store result to local variable for usage } n-=dn; // remove return value from the count double z; z=x; va_list va; va_start(va,x); n-=dn; for (;n>=0;n-=dn) { x=va_arg(va,double); if (z>x) z=x; } va_end(va); return z; }
Будьте осторожны, каждый компилятор может иметь различную последовательность вызова, поэтому сначала проверьте список сборок во время отладки перед использованием !!!
Используйте _vscprintf для определения длины списка переменных. https://msdn.microsoft.com/en-us/library/w05tbk72.aspx