Поиск…


Вступление

Аргументы переменной используются функциями семейства printf ( printf , fprintf и т. Д.) И другими, чтобы позволить функции вызываться с различным количеством аргументов каждый раз, поэтому имя varargs .

Чтобы реализовать функции с использованием функции аргументов переменных, используйте #include <stdarg.h> .

Чтобы вызывать функции, которые принимают переменное количество аргументов, убедитесь, что в области видимости имеется полный прототип с void err_exit(const char *format, ...); эллипсисом: void err_exit(const char *format, ...); например.

Синтаксис

  • void va_start (va_list ap, последний ); / * Запустить обработку вариационных аргументов; last - последний параметр функции перед многоточием («...») * /
  • тип va_arg (va_list ap, type ); / * Получить следующий переменный аргумент в списке; обязательно передайте правильный продвинутый тип * /
  • void va_end (va_list ap); / * Обработка конечных аргументов * /
  • void va_copy (va_list dst, va_list src); / * C99 или более поздняя версия: список аргументов копирования, то есть текущая позиция в обработке аргументов, в другой список (например, для передачи нескольких аргументов несколько раз) * /

параметры

параметр подробности
va_list ap указатель аргументов, текущая позиция в списке вариационных аргументов
прошлой имя последнего аргумента невариантной функции, поэтому компилятор находит правильное место для начала обработки вариативных аргументов; не может быть объявлена ​​как переменная register , функция или тип массива
тип расширенный тип VARIADIC аргумента для чтения (например , int для short int аргумента)
va_list src указатель текущего аргумента для копирования
va_list dst новый список аргументов, который должен быть заполнен

замечания

Функции va_start , va_arg , va_end и va_copy самом деле являются макросами.

Обязательно всегда вызывайте va_start сначала, и только один раз, и вызывать va_end последним, и только один раз, и на каждой точке выхода функции. Не делать этого может работать на вашей системе, но, безусловно, не переносится и, следовательно, вызывает ошибки.

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

При вызове va_arg вы должны запросить тип продвигаемого аргумента, а именно:

  • short повышается до intunsigned short также увеличивается до int если sizeof(unsigned short) == sizeof(int) , и в этом случае ему присваивается unsigned int ).
  • float повышается double .
  • signed char продвигается до int ; unsigned char также продвигается до int если sizeof(unsigned char) == sizeof(int) , что редко бывает.
  • char обычно повышается до int .
  • Аналогичным образом продвигаются такие типы C99, как uint8_t или int16_t .

Историческая (например, K & R) обработка вариационных аргументов объявляется в <varargs.h> но не должна использоваться, поскольку она устарела. Стандартная обработка вариационных аргументов (описанная здесь и объявленная в <stdarg.h> ) была введена в C89; макрос va_copy был введен в C99, но предоставлен многими компиляторами до этого.

Использование явного аргумента count для определения длины va_list

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

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

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

/* first arg is the number of following int args to sum. */
int sum(int n, ...) {
    int sum = 0;
    va_list it; /* hold information about the variadic argument list. */

    va_start(it, n); /* start variadic argument processing */
    while (n--)
      sum += va_arg(it, int); /* get and sum the next variadic argument */
    va_end(it); /* end variadic argument processing */

    return sum;
}

int main(void)
{
    printf("%d\n", sum(5, 1, 2, 3, 4, 5)); /* prints 15 */
    printf("%d\n", sum(10, 5, 9, 2, 5, 111, 6666, 42, 1, 43, -6218)); /* prints 666 */
    return 0;
}

Использование значений терминатора для определения конца va_list

При любой вариационной функции функция должна знать, как интерпретировать список аргументов переменных. «Традиционный» подход (например, printf ) заключается в том, чтобы указать количество аргументов впереди. Однако это не всегда хорошая идея:

/* First argument specifies the number of parameters; the remainder are also int */
extern int sum(int n, ...);

/* But it's far from obvious from the code. */
sum(5, 2, 1, 4, 3, 6)

/* What happens if i.e. one argument is removed later on? */
sum(5, 2, 1, 3, 6)  /* Disaster */

Иногда более удобно добавлять явный терминатор, примером которого является функция POSIX execlp() . Вот еще одна функция для вычисления суммы ряда double чисел:

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

/* Sums args up until the terminator NAN */
double sum (double x, ...) {
  double sum = 0;
  va_list va;

  va_start(va, x);
  for (; !isnan(x); x = va_arg(va, double)) {
    sum += x;
  }
  va_end(va);

  return sum;
}

int main (void) {
  printf("%g\n", sum(5., 2., 1., 4., 3., 6., NAN));
  printf("%g\n", sum(1, 0.5, 0.25, 0.125, 0.0625, 0.03125, NAN));
}

Хорошие значения терминатора:

  • целое (должно быть все положительное или неотрицательное) - 0 или -1
  • типы с плавающей точкой - NAN
  • типы указателей - NULL
  • типы перечислителей - какое-то особое значение

Реализация функций с помощью интерфейса `printf ()` -like

Одним из распространенных вариантов списков аргументов переменной длины является реализация функций, которые представляют собой тонкую оболочку вокруг семейства функций printf() . Одним из таких примеров является набор функций отчетности об ошибках.

errmsg.h

#ifndef ERRMSG_H_INCLUDED
#define ERRMSG_H_INCLUDED

#include <stdarg.h>
#include <stdnoreturn.h>    // C11

void verrmsg(int errnum, const char *fmt, va_list ap);
noreturn void errmsg(int exitcode, int errnum, const char *fmt, ...);
void warnmsg(int errnum, const char *fmt, ...);

#endif

Это пример с голубыми костями; такие пакеты могут быть очень сложными. Обычно программисты будут использовать либо errmsg() либо warnmsg() , которые сами используют verrmsg() внутри. Однако, если кто-то verrmsg() необходимость сделать больше, то verrmsg() функция verrmsg() будет полезна. Вы могли бы избежать воздействия на него до тех пор , пока есть потребность в нем ( YAGNI - ты не собираешься это нужно ), но потребность возникнет в конце концов (вам понадобится это - YAGNI).

errmsg.c

Этот код должен только пересылать переменные аргументы функции vfprintf() для вывода на стандартную ошибку. Он также сообщает системное сообщение об ошибке, соответствующее номеру системной ошибки ( errno ), переданному функциям.

#include "errmsg.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void
verrmsg(int errnum, const char *fmt, va_list ap)
{
    if (fmt)
        vfprintf(stderr, fmt, ap);
    if (errnum != 0)
        fprintf(stderr, ": %s", strerror(errnum));
    putc('\n', stderr);
}

void
errmsg(int exitcode, int errnum, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    verrmsg(errnum, fmt, ap);
    va_end(ap);
    exit(exitcode);
}

void
warnmsg(int errnum, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    verrmsg(errnum, fmt, ap);
    va_end(ap);
}

Использование errmsg.h

Теперь вы можете использовать эти функции следующим образом:

#include "errmsg.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    char buffer[BUFSIZ];
    int fd;
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s filename\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    const char *filename = argv[1];

    if ((fd = open(filename, O_RDONLY)) == -1)
        errmsg(EXIT_FAILURE, errno, "cannot open %s", filename);
    if (read(fd, buffer, sizeof(buffer)) != sizeof(buffer))
        errmsg(EXIT_FAILURE, errno, "cannot read %zu bytes from %s", sizeof(buffer), filename);
    if (close(fd) == -1)
        warnmsg(errno, "cannot close %s", filename);
    /* continue the program */
    return 0;
}

Если системные вызовы open() или read() не выполняются, ошибка записывается в стандартную ошибку, и программа выходит с кодом выхода 1. Если системный вызов close() не работает, ошибка просто печатается как предупреждающее сообщение и программа продолжается.

Проверка правильности использования форматов printf()

Если вы используете GCC (компилятор GNU C, который является частью сборника компиляторов GNU) или используя Clang, вы можете проверить, что аргументы, переданные в функции сообщений об ошибках, соответствуют тому, что ожидает printf() . Поскольку не все компиляторы поддерживают расширение, его необходимо скомпилировать условно, что немного затруднительно. Тем не менее, защита, которую она дает, стоит усилий.

Во-первых, нам нужно знать, как определить, что компилятор - это GCC или Clang, имитирующий GCC. Ответ заключается в том, что GCC определяет __GNUC__ чтобы указать это.

См. Общие атрибуты функций для получения информации об атрибутах - в частности format атрибута format .

Переписано errmsg.h

#ifndef ERRMSG_H_INCLUDED
#define ERRMSG_H_INCLUDED

#include <stdarg.h>
#include <stdnoreturn.h>    // C11

#if !defined(PRINTFLIKE)
#if defined(__GNUC__)
#define PRINTFLIKE(n,m) __attribute__((format(printf,n,m)))
#else
#define PRINTFLIKE(n,m) /* If only */
#endif /* __GNUC__ */
#endif /* PRINTFLIKE */

void verrmsg(int errnum, const char *fmt, va_list ap);
void noreturn errmsg(int exitcode, int errnum, const char *fmt, ...)
        PRINTFLIKE(3, 4);
void warnmsg(int errnum, const char *fmt, ...)
        PRINTFLIKE(2, 3);

#endif

Теперь, если вы допустили ошибку, например:

errmsg(EXIT_FAILURE, errno, "Failed to open file '%d' for reading", filename);

(где %d должно быть %s ), тогда компилятор будет жаловаться:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \
>     -Wold-style-definition -c erruse.c
erruse.c: In function ‘main’:
erruse.c:20:64: error: format ‘%d’ expects argument of type ‘int’, but argument 4 has type ‘const char *’ [-Werror=format=]
         errmsg(EXIT_FAILURE, errno, "Failed to open file '%d' for reading", filename);
                                                           ~^
                                                           %s
cc1: all warnings being treated as errors
$

Использование строки формата

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

В приведенном ниже примере показана функция aa, которая обертывает стандартную функцию printf() , только позволяя использовать вариационные аргументы типа char , int и double (в формате десятичной с плавающей запятой). Здесь, как и в случае с printf() , первым аргументом функции wrapping является строка формата. По мере разбора строки форматирования функция может определить, есть ли ожидаемый другой вариационный аргумент и каков его тип.

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

int simple_printf(const char *format, ...)
{
    va_list ap; /* hold information about the variadic argument list. */
    int printed = 0; /* count of printed characters */

    va_start(ap, format); /* start variadic argument processing */

    while (*format != '\0') /* read format string until string terminator */
    {
        int f = 0;

        if (*format == '%')
        {
            ++format;
            switch(*format)
            {
                case 'c' :
                    f = printf("%d", va_arg(ap, int)); /* print next variadic argument, note type promotion from char to int */
                    break;
                case 'd' :
                    f = printf("%d", va_arg(ap, int)); /* print next variadic argument */
                    break;

                case 'f' :
                    f = printf("%f", va_arg(ap, double)); /* print next variadic argument */
                    break;
                default :
                    f = -1; /* invalid format specifier */
                    break;
            }
        }
        else
        {
            f = printf("%c", *format); /* print any other characters */
        }

        if (f < 0) /* check for errors */
        {
            printed = f;
            break;
        }
        else
        {
            printed += f;
        }
        ++format; /* move on to next character in string */
    }

    va_end(ap); /* end variadic argument processing */

    return printed;
}

int main (int argc, char *argv[])
{
    int x = 40;
    int y = 0;

    y = simple_printf("There are %d characters in this sentence", x);
    simple_printf("\n%d were printed\n", y);
}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow