Функции с переменным числом аргументов в C/C++

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

Функции с переменным числом аргументов в C

Классическое использование функции, которая принимает переменное число параметров, выглядит так:

#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's __attribute__

Компиллятор 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++

Если Вы пишите функцию с переменным числом параметров в 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 указателем.

Макросы Variadic

Иногда может быть очень полезным иметь макрос с неизветным количеством аргументов. Хотя это выглядит очевидным, но было добавлено только в стандарт С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]).

Ссылки:

  1. Using GNU C __attribute__
  2. Variadic Macros (Linux)
  3. Variadic Macros (Windows)

Posted by Ami Chayun | Sunday, 3 Aug. 2008