Передача змінної кількості аргументів навколо


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
Приклад не має сенсу. Це було просто показати контур коду.
Vicent Marti

162
"повинен бути googled": Я не згоден. У Google багато шуму (незрозуміла, часто заплутана інформація). Гарна (проголосована, прийнята відповідь) на stackoverflow справді допомагає!
Ansgar

71
Тільки для зважування: Я прийшов до цього питання з google, і оскільки це було переповнено стеком, я був дуже впевнений, що відповідь буде корисною. Тож запитайте геть!
tenpn

32
@Ilya: якби ніхто ніколи не записував речі за межами Google, не було б інформації про пошук у Google.
Ерік Каплун

Відповіді:


211

Щоб передати еліпси, вам потрібно перетворити їх у 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 повертав інший рядок. Я відредагую відповідь, щоб показати останню.
SmacL

1
Якщо у вашому рядку формату використовується ті ж команди строкових форматів, що й printf, ви також можете отримати деякі компілятори, такі як gcc та clang, щоб попередити, якщо ваш форматний рядок не сумісний з фактично переданими аргументами. Див. Атрибут функції GCC ' формат "для отримання детальної інформації: gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html .
Дуг Річардсон

1
Здається, це не спрацює, якщо ви передаєте аргументи двічі поспіль.
fotanus

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

Варіатичні функції можуть бути небезпечними . Ось більш безпечний трюк:

   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 я не впевнений, що згоден, що це поганий злом, у Rose є чудове рішення, але воно включає введення func ((type []) {val1, val2, 0}); який відчуває незграбність, коли б у вас був #define func_short_cut (...) func ((тип []) { 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...);
}

Не забувайте, що різноманітні шаблони все ще недоступні у Visual Studio ... це, звичайно, може вас не хвилювати!
Tom Swirly

1
Якщо ви використовуєте Visual Studio, варіативні шаблони можна додати до Visual Studio 2012 за допомогою CTP листопада 2012 року. Якщо ви використовуєте Visual Studio 2013, у вас будуть різні шаблони.
користувач2023370

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

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

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

Сподіваюсь, це допомагає.


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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.