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

333

Скажем, у меня есть функция 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
Пример не имеет смысла. Это было просто, чтобы показать схему кода.
Висент Марти
163
«надо гуглить»: я не согласен. У Google много шума (неясная, часто запутанная информация). Наличие хорошего (проголосовавшего, принятого ответа) на stackoverflow действительно помогает!
Ансгар
71
Просто чтобы взвесить: я пришел к этому вопросу из Google, и потому что это было переполнение стека, я был очень уверен, что ответ будет полезным. Так что спросите!
10
32
@ Илья: если бы никто не записывал материал за пределами Google, не было бы информации для поиска в Google.
Эрик Каплун

Ответы:

212

Чтобы передать эллипсы, вы должны преобразовать их в 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);
}
SmacL
источник
3
Код взят из вопроса и на самом деле является просто иллюстрацией того, как преобразовывать эллипсы, а не что-либо функциональное. Если вы посмотрите на это, format_stringто вряд ли это будет полезно, так как придется вносить изменения на месте в fmt, что, конечно же, не следует делать. Опции могут включать полное избавление от format_string и использование vfprintf, но это делает предположения о том, что фактически делает format_string, или если format_string возвращает другую строку. Я отредактирую ответ, чтобы показать последнее.
SmacL
1
Если ваша строка формата использует те же команды строки формата, что и printf, вы также можете получить некоторые компиляторы, такие как gcc и clang, которые будут предупреждать вас, если ваша строка формата не совместима с фактическими переданными аргументами. См. Атрибут функции GCC ' формат 'для более подробной информации: gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html .
Даг Ричардсон
1
Это не работает, если вы передаете аргументы дважды подряд.
Фотанус
2
@fotanus: если вы вызываете функцию с помощью argptrи вызываемая функция argptrвообще использует функцию , единственное, что можно сделать безопасно, это вызвать va_end()и затем перезапустить va_start(argptr, fmt);для повторной инициализации. Или вы можете использовать, va_copy()если ваша система поддерживает это (C99 и C11 требуют этого; C89 / 90 этого не сделали).
Джонатан Леффлер
1
Обратите внимание, что комментарий @ ThomasPadron-McCarthy устарел, и окончательный fprintf в порядке.
Фредерик
59

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

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


источник
Обратите внимание, что vsyslogэто не POSIX- совместимый.
patryk.beza
53

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

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

func((type[]){val1,val2,val3,val4,0});
Роза Перроне
источник
11
Еще лучше этот трюк: #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
Ричард Дж. Росс III
5
@ RichardJ.RossIII Хотелось бы, чтобы вы расширили свой комментарий, его вряд ли можно было бы так прочитать, я не могу разобраться в идее кода, и на самом деле он выглядит очень интересным и полезным.
Пенелопа
5
@ArtOfWarfare Я не уверен, что согласен с тем, что это плохой хак, у Роуз есть отличное решение, но оно включает в себя ввод func ((type []) {val1, val2, 0}); что кажется неуклюжим, если у вас есть #define func_short_cut (...) func ((type []) { VA_ARGS }); тогда вы можете просто вызвать func_short_cut (1, 2, 3, 4, 0); который дает вам тот же синтаксис, что и обычная функция с переменным числом аргументов, с дополнительным преимуществом аккуратного трюка Роуза ... в чем здесь проблема?
chrispepper1989
9
Что если вы хотите передать 0 в качестве аргумента?
Джулиан Голд
1
Это требует, чтобы ваши пользователи не забывали звонить с завершающим 0. Насколько это безопаснее?
cp.engr
29

В великолепном 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...);
}
user2023370
источник
Не забывайте, что в Visual Studio все еще не доступны шаблоны с переменными параметрами ... это, конечно, может вас не беспокоить!
Том Свирли,
1
Если вы используете Visual Studio, шаблоны переменных могут быть добавлены в Visual Studio 2012 с использованием CTP-версии ноября 2012 года. Если вы используете Visual Studio 2013, у вас будут различные шаблоны.
user2023370
7

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

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);
    }
Yoda
источник
4
Не переносимый, зависит от компилятора и препятствует оптимизации компилятора. Очень плохое решение.
Джеффрой
4
Новый без удаления тоже.
user7116
8
По крайней мере, это действительно отвечает на вопрос, не переопределяя вопрос.
lama12345
6

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

#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;
}
GeekyJ
источник
6

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

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

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

VarunG
источник
2

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

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

Или ты?

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

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

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

инженер
источник
-5

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

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

Джим
источник