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

StackOverflow https://stackoverflow.com/questions/205529

  •  03-07-2019
  •  | 
  •  

Вопрос

Скажем, у меня есть функция 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);
 }
Это было полезно?

Решение

Чтобы передать эллипсы, вам нужно преобразовать их в 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);
}

Другие советы

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

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

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

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

func((type[]){val1,val2,val3,val4,0});

В великолепном 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...);
}

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

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);
    }

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

#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;
}

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

#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");
}

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

Решение Росса немного подчистилось.Работает только в том случае, если все аргументы являются указателями.Также реализация языка должна поддерживать исключение предыдущей запятой, если __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);}

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

Или ты?

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

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

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

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

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);
}

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top