Передача переменного количества аргументов вокруг

269

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

Пример:

void format_string(char *fmt, ...);

void debug_print(int dbg_lvl, char *fmt, ...) {
    format_string(fmt, /* how do I pass all the arguments from '...'? */);
    fprintf(stdout, fmt);
 }
  • 4
    Ваш пример выглядит немного странно для меня, так как вы передаете fmt как format_string (), так и fprintf (). Должен ли format_string () возвращать новую строку?
  • 2
    Пример не имеет смысла. Это было просто, чтобы показать схему кода.
Показать ещё 3 комментария
Теги:
variadic-functions

9 ответов

170
Лучший ответ

Чтобы передать эллипсы, вы должны преобразовать их в va_list и использовать этот va_list во второй функции. В частности,

void format_string(char *fmt,va_list argptr, char *formatted_string);


void debug_print(int dbg_lvl, char *fmt, ...) 
{    
 char formatted_string[MAX_FMT_SIZE];

 va_list argptr;
 va_start(argptr,fmt);
 format_string(fmt, argptr, formatted_string);
 va_end(argptr);
 fprintf(stdout, "%s",formatted_string);
}
  • 3
    Код взят из вопроса и на самом деле является просто иллюстрацией того, как преобразовывать эллипсы, а не что-либо функциональное. Если вы посмотрите на него, то format_string тоже вряд ли будет полезна, так как для него придется вносить изменения в fmt на месте, что, конечно же, не следует делать. Опции могут включать полное избавление от format_string и использование vfprintf, но это делает предположения о том, что фактически делает format_string, или если format_string возвращает другую строку. Я отредактирую ответ, чтобы показать последнее.
  • 1
    Если ваша строка формата использует те же команды строки формата, что и printf, вы также можете получить некоторые компиляторы, такие как gcc и clang, которые будут предупреждать вас, если ваша строка формата не совместима с фактическими переданными аргументами. См. Атрибут функции GCC ' формат 'для более подробной информации: gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html .
Показать ещё 3 комментария
52

Функции Variadic могут быть опасными. Здесь более безопасный трюк:

   void func(type* values) {
        while(*values) {
            x = *values++;
            /* do whatever with x */
        }
    }

func((type[]){val1,val2,val3,val4,0});
  • 9
    Еще лучше этот трюк: #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
  • 4
    @ RichardJ.RossIII Хотелось бы, чтобы вы расширили свой комментарий, его вряд ли можно было бы так прочитать, я не могу разобраться в идее кода, и на самом деле он выглядит очень интересным и полезным.
Показать ещё 12 комментариев
49

Нет способа вызвать (например) printf, не зная, сколько аргументов вы переходите к нему, если вы не хотите попасть в непослушные и непереносимые трюки.

Обычно используемое решение состоит в том, чтобы всегда предоставлять альтернативную форму функций vararg, поэтому printf имеет vprintf, который вместо ... принимает va_list. Версия ... - это всего лишь обертки вокруг версий va_list.

  • 0
    Обратите внимание, что vsyslog не совместим с POSIX .
29

В великолепном С++ 0x вы можете использовать вариативные шаблоны:

template <typename ... Ts>
void format_string(char *fmt, Ts ... ts) {}

template <typename ... Ts>
void debug_print(int dbg_lvl, char *fmt, Ts ... ts)
{
  format_string(fmt, ts...);
}
  • 0
    Не забывайте, что в Visual Studio все еще не доступны шаблоны с переменными параметрами ... это, конечно, может вас не беспокоить!
  • 1
    Если вы используете Visual Studio, в Visual Studio 2012 можно добавить шаблоны с переменными параметрами, используя ноябрьскую CTP-версию 2012 года. Если вы используете Visual Studio 2013, у вас будут различные шаблоны.
4

Вы можете использовать встроенную сборку для вызова функции. (в этом коде я предполагаю, что аргументы являются символами).

void format_string(char *fmt, ...);
void debug_print(int dbg_level, int numOfArgs, char *fmt, ...)
    {
        va_list argumentsToPass;
        va_start(argumentsToPass, fmt);
        char *list = new char[numOfArgs];
        for(int n = 0; n < numOfArgs; n++)
            list[n] = va_arg(argumentsToPass, char);
        va_end(argumentsToPass);
        for(int n = numOfArgs - 1; n >= 0; n--)
        {
            char next;
            next = list[n];
            __asm push next;
        }
        __asm push fmt;
        __asm call format_string;
        fprintf(stdout, fmt);
    }
  • 3
    Не переносимый, зависит от компилятора и препятствует оптимизации компилятора. Очень плохое решение.
  • 4
    Новый без удаления тоже.
Показать ещё 2 комментария
1

Вы также можете попробовать макрос.

#define NONE    0x00
#define DBG     0x1F
#define INFO    0x0F
#define ERR     0x07
#define EMR     0x03
#define CRIT    0x01

#define DEBUG_LEVEL ERR

#define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: "
#define WHEREARG __FILE__,__func__,__LINE__
#define DEBUG(...)  fprintf(stderr, __VA_ARGS__)
#define DEBUG_PRINT(X, _fmt, ...)  if((DEBUG_LEVEL & X) == X) \
                                      DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__)

int main()
{
    int x=10;
    DEBUG_PRINT(DBG, "i am x %d\n", x);
    return 0;
}
1

Решение Росса немного очистилось. Работает только в том случае, если все аргументы являются указателями. Кроме того, реализация языка должна поддерживать возврат предыдущей запятой, если __VA_ARGS__ пуст (как Visual Studio С++, так и GCC).

// pass number of arguments version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__}; _actualFunction(args+1,sizeof(args) / sizeof(*args) - 1);}


// NULL terminated array version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__, NULL}; _actualFunction(args+1);}
-1

Скажем, у вас есть типичная вариационная функция, которую вы написали. Поскольку по крайней мере один аргумент требуется перед вариационным ..., вы должны всегда писать дополнительный аргумент в использовании.

Или вы?

Если вы переносите свою вариационную функцию в макрос, вам не нужен предшествующий arg. Рассмотрим этот пример:

#define LOGI(...)
    ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))

Это, безусловно, гораздо удобнее, так как вам не нужно указывать начальный аргумент каждый раз.

-4

Я не уверен, что это работает для всех компиляторов, но он до сих пор работал у меня.

void inner_func(int &i)
{
  va_list vars;
  va_start(vars, i);
  int j = va_arg(vars);
  va_end(vars); // Generally useless, but should be included.
}

void func(int i, ...)
{
  inner_func(i);
}

Вы можете добавить... в inner_func(), если хотите, но вам это не нужно. Он работает, потому что va_start использует адрес данной переменной в качестве начальной точки. В этом случае мы даем ссылку на переменную в func(). Поэтому он использует этот адрес и считывает переменные после этого в стеке. Функция inner_func() считывает из адреса стека func(). Таким образом, он работает только в том случае, если обе функции используют один и тот же сегмент стека.

Макросы va_start и va_arg обычно будут работать, если вы дадите им любой var в качестве отправной точки. Поэтому, если вы хотите, вы можете передавать указатели на другие функции и использовать их тоже. Вы можете сделать свои собственные макросы достаточно легко. Все макросы - это адреса памяти типов. Однако заставить их работать для всех компиляторов и вызывающих конвенций раздражает. Поэтому, как правило, проще использовать те, которые поставляются вместе с компилятором.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню