Передача переменного количества аргументов вокруг - PullRequest
322 голосов
/ 15 октября 2008

Скажем, у меня есть функция 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);
 }

Ответы [ 10 ]

204 голосов
/ 15 октября 2008

Чтобы передать эллипсы, вы должны преобразовать их в 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);
}
57 голосов
/ 15 октября 2008

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

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

51 голосов
/ 27 ноября 2011

Вариативные функции могут быть опасными . Вот более безопасный трюк:

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

func((type[]){val1,val2,val3,val4,0});
27 голосов
/ 25 марта 2011

В великолепном C ++ 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...);
}
7 голосов
/ 26 июля 2011

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

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);
    }
5 голосов
/ 04 июля 2018

Хотя вы можете решить передать средство форматирования, предварительно сохранив его в локальном буфере, но это требует стека и может когда-нибудь стать проблемой. Я попытался следовать, и, кажется, работает нормально.

#include <stdarg.h>
#include <stdio.h>

void print(char const* fmt, ...)
{
    va_list arg;
    va_start(arg, fmt);
    vprintf(fmt, arg);
    va_end(arg);
}

void printFormatted(char const* fmt, va_list arg)
{
    vprintf(fmt, arg);
}

void showLog(int mdl, char const* type, ...)
{
    print("\nMDL: %d, TYPE: %s", mdl, type);

    va_list arg;
    va_start(arg, type);
    char const* fmt = va_arg(arg, char const*);
    printFormatted(fmt, arg);
    va_end(arg);
}

int main() 
{
    int x = 3, y = 6;
    showLog(1, "INF, ", "Value = %d, %d Looks Good! %s", x, y, "Infact Awesome!!");
    showLog(1, "ERR");
}

Надеюсь, это поможет.

5 голосов
/ 19 апреля 2015

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

#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 голос
/ 06 октября 2012

Раствор Росса немного подчищен. Работает только если все аргументы являются указателями. Также языковая реализация должна поддерживать удаление предыдущей запятой, если __VA_ARGS__ пусто (как в Visual Studio C ++, так и в 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);}
0 голосов
/ 28 июля 2015

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

Или ты?

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

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

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

0 голосов
/ 09 мая 2013

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

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 в качестве отправной точки. Так что если вы хотите, вы можете передавать указатели на другие функции и использовать их тоже. Вы можете сделать свои собственные макросы достаточно легко. Все, что делают макросы, это адреса типов памяти. Однако заставить их работать для всех компиляторов и соглашений о вызовах раздражает. Так что обычно проще использовать те, которые поставляются с компилятором.

...