수색…


소개

가변 인수 는 printf 계열 ( printf , fprintf 등)의 함수와 매번 다른 수의 인수로 함수를 호출 할 수 있도록하는 다른 함수에 사용됩니다. 따라서 varargs 라는 이름이 사용됩니다.

가변 인수 기능을 사용하여 함수를 구현하려면 #include <stdarg.h> .

가변 개수의 인수를 취하는 함수를 호출하려면, 범위 내에있는 생략 부호를 가진 완전한 프로토 타입이 있어야합니다 : void err_exit(const char *format, ...); 예를 들면.

통사론

  • void va_start (va_list ap, last ); / * 가변 인수 처리를 시작합니다. 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 변수, 함수 또는 배열 유형으로 선언 할 수 없습니다.
유형 승격 된 가변 인수의 형태 (예를 들어, int short int 인수)
va_list src 카피하는 현재의 인수 포인터
va_list dst 채울 새 인수 목록

비고

va_start , va_arg , va_endva_copy 함수는 실제로는 매크로입니다.

항상 호출해야합니다 va_start 처음 한 번만, 그리고 전화를 va_end 한 번만 마지막으로하고, 그 함수의 모든 종료 지점. 아니 따라서 귀하의 시스템에서 작동 할 있도록하고 그러나 확실하게 이식 할 수 없습니다 및 버그를 초대합니다.

프로토 타입을 사용하여 함수를 올바르게 선언하고 마지막 가변적이지 않은 인수 (함수 또는 배열 유형이 아닌 register 가 아님)에 대한 제한을 염두에 두십시오. 인수 처리를 시작할 수 있으려면 적어도 하나의 변수가 아닌 인수가 필요하기 때문에 가변 인수 만 사용하는 함수는 선언 할 수 없습니다.

va_arg 호출 할 때 va_arg 과 같은 승격 된 인수 유형을 요청해야합니다.

  • short 로 승격 int (및 unsigned short 에도 촉진 int 않는 sizeof(unsigned short) == sizeof(int) ,이 경우에이 촉진 unsigned int ).
  • floatdouble 증진됩니다.
  • signed charint 로 승격됩니다. unsigned charsizeof(unsigned char) == sizeof(int) 아닌 경우 int 승격됩니다.
  • char 는 일반적으로 int 승격됩니다.
  • uint8_t 또는 int16_t 와 같은 C99 유형도 유사하게 승격됩니다.

역사적인 (즉, 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 ()`와 같은 인터페이스로 함수 구현하기

가변 길이 인수리스트의 일반적인 사용법 중 하나는 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

이 코드는 variadic 인수를 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 Compiler Collection의 일부인 GNU C 컴파일러)를 사용하거나 Clang을 사용하는 경우 컴파일러에서 오류 메시지 함수에 전달하는 인수가 printf() 예상하는 것과 일치하는지 확인하도록 할 수 있습니다. 모든 컴파일러가 확장 기능을 지원하는 것은 아니기 때문에 조건부로 컴파일해야합니다. 그러나 그것이 제공하는 보호는 노력할만한 가치가 있습니다.

먼저, 컴파일러가 GCC 또는 GCC를 모방하는 Clang인지 확인하는 방법을 알아야합니다. 대답은 GCC __GNUC__ 를 나타 내기 위해 __GNUC__ 을 정의 __GNUC__ 것입니다.

속성에 대한 정보는 공통 함수 속성 - 특히 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
$

형식 문자열 사용

형식 문자열을 사용하면 명시 적 개수 인수 나 종결 자 값이 필요하지 않도록 후속 가변 인수의 예상 개수와 유형에 대한 정보가 제공됩니다.

아래 예제는 표준 printf() 함수를 감싸는 함수로, char , intdouble 형식의 가변 인수를 사용할 수 있습니다 (10 진수 부동 소수점 형식). printf() 와 마찬가지로, 래핑 함수의 첫 번째 인수는 형식 문자열입니다. 형식 문자열이 구문 분석 될 때 함수는 예상되는 또 다른 가변 인수가 있는지, 유형이 무엇인지 판단 할 수 있습니다.

#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