Назва

stdarg - змінний список аргументів

Огляд

#include <stdarg.h>

void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);

Опис

Функція може бути викликаною з різноманітною кількістю аргументів різноманітних типів. Файл-заголовок stdarg.h оголошує тип va_list і визначає три макроси для проходження через список аргументів, чия кількість і тип невідомі викликаній функції.

Викликана функція повинна оголосити об'єкт типу va_list, який використовуватиметься макросами va_start, va_arg і va_end.

va_start

Макрос va_start ініціалізує ap для пізнішого використання va_arg і va_end і повинен бути викликаним першим.

Параметр last є назвою останнього параметру перед змінним списком аргументів, тобто останній параметр, чий тип відомий викликаючій функції.

Оскільки адреса цього параметру може використовуватись у va_start макросі, її не слід оголошувати як регістрова змінна, або як функція, або ж як масивовий тип.

va_arg

Макрос va_arg розкривається у вираз, що матиме тип і значення наступного аргумету виклику. Параметр ap є типу va_list, ініційований va_start. Кожний виклик va_arg змінює ap так, що кожний новий виклик повертає наступний аргумент. Параметр type є назвою типу, тож покажчик на об'єкт певного типу можна вказати просто додаючи * за вказаним типом.

Перше використання макросу va_arg після va_start повертає аргумент, що слідує за останнім відомим аргументом, last. Послідовні виклики повертають значення решти аргументів.

Якщо наступний аргумент відсутній або, якщо тип є несумісним з типом дійсного наступного аргументу, відбудеться довільна помилка.

Якщо ap передано функції, що використовує va_arg(ap, type), тоді значення ap буде невизначеним після повернення цією функцією.

va_end

Кожний виклик va_start повинен мати відповідний va_end у тій самій функції. Після виклику va_end(ap) змінна ap стане невизначеною. Можливі багатократні проходження списків, кожне з яких повинно бути включеним у va_start і va_end. va_end може бути як макросом, так і функцією.

va_copy

Стандартна реалізація, як правило, має va_list як покажчик на фрейм стеку зміцнювальної функції. При такій (найпоширенішій) схемі, схоже, нічого не заперечує присвоєнню на зразок

       va_list aq = ap;

На жаль, існують також системи, які роблять va_list масивом покажчиків (довжиною 1) і де необхідно

       va_list aq;
       *aq = *ap;

І, накінець, на системах, де параметри передаються через регістри, може виникнути необхідність виділення пам'яті функцією va_start, збереження там параметрів, крім того вказівник, який параметр є наступним, тож va_arg зможе пройти крізь список. Після чого va_end зможе звільнити виділену пам'ять знову. Щоб задовільнити ці вимоги, C99 додав макрос va_copy, тож присвоєння вище можна замінити на

       va_list aq;
       va_copy(aq, ap);
       ...
       va_end(aq);

Кожний виклик va_copy повинен співпадати з відповідним va_end у тій самій функції. Деякі системи, що не надають .BР va_copy , мають __va_copy натомість, оскільки саме ця назва використовувалась у чорновій пропозиції.

Приклад

Функція sum, яка додає довільну кількість цілих.

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

       void sum(char *, int, ...);

       int main(void)
       {
           sum("The sum of 10+15+13 is %d.\n", 3, 10, 15, 13);
           return 0;
       }

       void sum(char *string, int num_args, ...)
       {
           int sum = 0;
           va_list ap;
           int i;

           va_start(ap, num_args);    /* ініціалізація ap */
           for(i = 0; i < num_args; i++)
               sum += va_arg(ap, int); /* проходження ap  *
                                        * через аргументи */
           printf(string, sum);
           va_end(ap);
       }

Функція fprint, яка візьме список форматуючих символів і виведе аргументи у відповідності з форматуючим символом, основуючись на типі.

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

       void fprint(char *fmt, ...) {
           va_list ap;
           int d;
           char c, *p, *s;

           va_start(ap, fmt);
           while (*fmt)
               switch(*fmt++) {
               case 's':           /* ланцюжок */
                   s = va_arg(ap, char *);
                   printf("string %s\n", s);
                   break;
               case 'd':           /* ціле int */
                   d = va_arg(ap, int);
                   printf("int %d\n", d);
                   break;
               case 'c':           /* символ char */
                   /* необхідне зведення типів, оскільки va_arg *
                    * візьме тільки повністю сформований тип    */
                      c = (char) va_arg(ap, int);
                      printf("char %c\n", c);
                      break;
               }
           va_end(ap);
       }

Відповідність стандартам

Макроси va_start, va_arg і va_end відповідають ANSI X3.159-1989 ("C89"). C99 дало визначення макросу va_copy.

Сумісність

Ці макроси не сумісні з історичними версіями, які вони замінили. Обернено-сумісну версію можна знайти у заголовковому файлі varargs.h.

Порівняння

Історична версія виглядатиме наступним чином:

       #include <varargs.h>

       void fprint(va_alist) va_dcl {
           va_list ap;

           va_start(ap);
           while(...) {
               ...
               x = va_arg(ap, type);
               ...
           }
           va_end(ap);
       }

На деяких системах, va_end містить закриваючу }', що співпадатиме з відкриваючою{' va_start, тож обидва макроси повинні знаходитись у тій самій функції і, в якійсь мірі, фігурні дужки примушують до цього.

Вади

На відміну від макросів varargs.h, розглянуті тут не дозволяють програмістам кодування функцій без сталих аргументів. Ця проблема додає роботи, головним чином, коли доводиться переводити код varargs до stdarg, але це також створює труднощі у випадку змінювальних функцій, які хочуть передати всі свої аргументи іншій, яка візьме аргументи типу va_list, наприклад ?vfprintf(3).