Во многих языках программирования разрешается определять функции с переменным числом параметров. Несмотря на то, что этот метод обладает большой силой, использовать его надо аккуратно. В этом обзоре мы будем исследовать использование функций и макросов с переменным числом параметров, а так же - наиболее распространённые ловушки.
Классическое использование функции, которая принимает переменное число параметров, выглядит так:
#include <stdarg.h> #include <stdio.h> enum { ERROR_LOG, WARN_LOG, INFO_LOG, VERBOSE_LOG, DEBUG_LOG } log_levels; const char *log_levels_str[] = { "Error", "Warning", "Info", "Verbose", "Debug", }; int applog(int level, const char *fmt, ...) { int ret; FILE *stream; va_list ap; va_start(ap, fmt); (level <= WARN_LOG) ? (stream = stderr) : (stream = stdout); fprintf(stream, "%s: ", log_levels_str[level]); ret = vfprintf(stream, fmt, ap); va_end(ap); return ret; }
Вызов va_start инициализирует переменную up которая становится правее fmt. После этого вызова можно использовать up для доступа к параметрам, используя va_arg, или передавая их другим функциям.
Компиллятор GCC предлагает отличную защиту от неправильного использования функций с переменным числом параметров, ещё во время компилляции. Точно так же, как для функции 'printf' компиллятор создаёт сообщения об ошибках, возможно защитить и Вашу реализацию функции с переменным числом параметров. Например:
//If the compiler does not support attributes, disable them #ifndef __GNUC__ # define __attribute__(x) #endif int applog(int level, const char *fmt, ...) __attribute__ ((format(printf, 2, 3) ));
Во время компиляции __attribute__ будет выполнять проверку на соблюдение стиля вызова printf и будет пытаться найти соответствие параметру #2 (fmt) как строку форматирования. Аргумент #3 функции applog будет помечен как начало списка параметров переменной длины. Кроме того, атрибуты формата поддерживают следующие методы проверки: scanf, strftime и strfmon. Этот атрибут поддерживается во всех версиях GCC, начиная с 2.Х и выше. Для более детальной информации об этом атрибуте (и других) см [1]
Если Вы пишите функцию с переменным числом параметров в C++, Вы будете думать, что это правильно :
class A { ... int myprintf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); };
Атрибут форматирования должен использовать первый параемтр как строку форматирования и второй - как переменную часть. Однако компилляция такого кода в GCC приведёт к ошибке:
error: format string argument not a string type>
Разумеется, это следствие того факта, что в C++ имеется один дополнительный аргумент в каждом члене класса. Для исправления этой ошибки необходимо сделать так:
class A { ... int myprintf(const char *fmt, ...) __attribute__((format(printf, 2, 3))); };
Кроме передачи структуры va_list в различные функции, возможно проходить по переменным функции вручную. Это может выглядеть как-то так:
int sum_many(int first, ...) __attribute__((sentinel(0))); int sum_many(int first, ...) { int num, ret = first; va_list ap; va_start(ap, first); while ( (num = va_arg(ap, int)) != NULL) { ret += num; } va_end(ap); return ret; }
Каждый вызов va_arg будет возвращать следующий аргумент. Функция va_arg требуется имя типа, или указатель на имя типа, для преобразования в результирующий аргумент.
Атрибут sentinel(0) задаёт проверку во время выполнения того, что аргумент в позиции 0 (последний аргумент функции) является NULL указателем.
Иногда может быть очень полезным иметь макрос с неизветным количеством аргументов. Хотя это выглядит очевидным, но было добавлено только в стандарт С99. Ниже идёт пример использования такого макроса:
#define LOG_DEBUG(fmt, ...) (applog(DEBUG_LOG, "[%s at %s:%u]: " fmt, \ __FUNCTION__, __FILE__, __LINE__, __VA_ARGS__))
Макросы Variadic поддерживаются всеми компилляторами, совместимыми с С99. GCC 3.3 и старше, Microsoft Windows Visual Studio 2005 и старше (см [3]).
Posted by Ami Chayun | Sunday, 3 Aug. 2008