C Language
Переменные аргументы
Поиск…
Вступление
Аргументы переменной используются функциями семейства 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
повышается доint
(иunsigned 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);
}