Szukaj…


Wprowadzenie

Zmienne argumenty są używane przez funkcje z rodziny printf ( printf , fprintf , itp.) I inne, aby umożliwić wywoływanie funkcji za pomocą innej liczby argumentów za każdym razem, stąd nazwa varargs .

Aby zaimplementować funkcje za pomocą funkcji zmiennych argumentów, użyj #include <stdarg.h> .

Aby wywoływać funkcje, które przyjmują zmienną liczbę argumentów, upewnij się, że istnieje pełny prototyp z końcową wielokropkiem w zakresie: void err_exit(const char *format, ...); na przykład.

Składnia

  • void va_start (va_list ap, last ); / * Rozpocznij przetwarzanie argumentów variadic; last jest ostatnim parametrem funkcji przed elipsą („...”) * /
  • wpisz va_arg (va_list ap, typ ); / * Uzyskaj następny argument variadic na liście; pamiętaj o podaniu prawidłowego promowanego typu * /
  • void va_end (va_list ap); / * Zakończ przetwarzanie argumentów * /
  • void va_copy (va_list dst, va_list src); / * C99 lub nowszy: skopiuj listę argumentów, tj. Bieżącą pozycję w przetwarzaniu argumentów, na inną listę (np. W celu wielokrotnego przekazania argumentów) * /

Parametry

Parametr Detale
va_list ap wskaźnik argumentów, aktualna pozycja na liście argumentów variadic
ostatni, ubiegły, zeszły nazwa ostatniego argumentu funkcji innej niż variadic, więc kompilator znajdzie właściwe miejsce do rozpoczęcia przetwarzania argumentów variadic; nie może być zadeklarowany jako zmienna register , funkcja lub typ tablicy
rodzaj promowany typ argumentu variadic do przeczytania (np. int dla short int argumentu short int )
va_list src wskaźnik bieżącego argumentu do skopiowania
va_list dst nowa lista argumentów do wypełnienia

Uwagi

Funkcje va_start , va_arg , va_end i va_copy są w rzeczywistości makrami.

Pamiętaj, aby zawsze wywoływać va_start najpierw i tylko raz, i wywoływać va_end końcu, i tylko raz, w każdym punkcie wyjścia funkcji. Nieprzestrzeganie tego może działać w twoim systemie, ale z pewnością nie jest przenośne i dlatego może powodować błędy.

Zadbaj o prawidłowe zadeklarowanie swojej funkcji, tj. Za pomocą prototypu, i zwróć uwagę na ograniczenia dotyczące ostatniego nie-variadycznego argumentu (nie register , nie funkcji lub typu tablicy). Nie można zadeklarować funkcji, która przyjmuje tylko argumenty variadic, ponieważ co najmniej jeden argument non-variadic jest potrzebny, aby móc rozpocząć przetwarzanie argumentów.

Podczas wywoływania va_arg musisz zażądać promowanego typu argumentu, to znaczy:

  • short jest promowany na int (i unsigned short jest również promowany na int chyba że sizeof(unsigned short) == sizeof(int) , w którym to przypadku jest promowany na unsigned int ).
  • float jest promowany do double .
  • signed char jest promowany do int ; unsigned char jest również promowany na int chyba że sizeof(unsigned char) == sizeof(int) , co rzadko ma miejsce.
  • char jest zwykle promowany do int .
  • Typy C99, takie jak uint8_t lub int16_t są podobnie promowane.

Historyczne (tj. K&R) przetwarzanie argumentów variadic jest zadeklarowane w <varargs.h> ale nie powinno się go stosować, ponieważ jest przestarzałe. Standardowe przetwarzanie argumentów variadic (opisane tutaj i zadeklarowane w <stdarg.h> ) zostało wprowadzone w C89; makro va_copy zostało wprowadzone w C99, ale wcześniej zostało dostarczone przez wiele kompilatorów.

Użycie jawnego argumentu zliczającego w celu ustalenia długości listy va_list

W przypadku każdej funkcji variadic funkcja musi wiedzieć, jak interpretować listę argumentów zmiennych. W przypadku printf() lub scanf() ciąg formatujący mówi funkcji, czego się spodziewać.

Najprostszą techniką jest przekazanie jawnej liczby innych argumentów (które zwykle są tego samego typu). Jest to pokazane w funkcji variadic w poniższym kodzie, który oblicza sumę szeregu liczb całkowitych, gdzie może być dowolna liczba liczb całkowitych, ale liczba ta jest określona jako argument przed listą zmiennych.

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

Korzystanie z wartości terminatora w celu ustalenia końca listy va_list

W przypadku każdej funkcji variadic funkcja musi wiedzieć, jak interpretować listę argumentów zmiennych. Podejście „tradycyjne” (na przykładzie printf ) polega na określeniu z góry liczby argumentów. Jednak nie zawsze jest to dobry pomysł:

/* 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 */

Czasami bardziej solidne jest dodanie jawnego terminatora, czego przykładem jest funkcja execlp() POSIX. Oto kolejna funkcja do obliczania sumy serii double liczb:

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

Dobre wartości terminatora:

  • liczba całkowita (powinna być dodatnia lub nieujemna) - 0 lub -1
  • typy zmiennoprzecinkowe - NAN
  • typy wskaźników - NULL
  • typy wyliczające - niektóre wartości specjalne

Implementowanie funkcji z interfejsem podobnym do `printf ()`

Jednym z powszechnych zastosowań list argumentów o zmiennej długości jest implementacja funkcji, które są cienkim opakowaniem wokół rodziny funkcji printf() . Jednym z takich przykładów jest zestaw funkcji raportowania błędów.

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

To jest prosty przykład; takie pakiety mogą być bardzo skomplikowane. Zwykle programiści używają albo errmsg() albo warnmsg() , którzy sami używają verrmsg() wewnętrznie. Jeśli jednak pojawi się potrzeba zrobienia więcej, przydatna będzie funkcja verrmsg() . Można uniknąć narażania go dopóki nie masz takiej potrzeby ( YAGNI - nie są gonna go potrzebuję ), ale potrzeba będzie wynikać ostatecznie (masz zamiar to potrzebne - YAGNI).

errmsg.c

Ten kod musi tylko przekazywać argumenty variadic do funkcji vfprintf() celu wyprowadzenia na błąd standardowy. Zgłasza także komunikat o błędzie systemu odpowiadający numerowi błędu systemu ( errno ) przekazanemu do funkcji.

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

Korzystanie z errmsg.h

Teraz możesz korzystać z tych funkcji w następujący sposób:

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

Jeśli wywołanie systemowe open() lub read() zakończy się niepowodzeniem, błąd zostanie zapisany jako błąd standardowy, a program zakończy działanie z kodem wyjścia 1. Jeśli wywołanie systemowe close() zakończy się niepowodzeniem, błąd zostanie wydrukowany jako komunikat ostrzegawczy, a program trwa.

Sprawdzanie poprawności użycia formatów printf()

Jeśli używasz GCC (kompilatora GNU C, który jest częścią kolekcji kompilatora GNU) lub Clanga, możesz poprosić kompilator o sprawdzenie, czy argumenty przekazywane do funkcji komunikatu o błędzie są zgodne z oczekiwaniami printf() . Ponieważ nie wszystkie kompilatory obsługują to rozszerzenie, należy je skompilować warunkowo, co jest nieco kłopotliwe. Jednak ochrona, którą daje, jest warta wysiłku.

Po pierwsze, musimy wiedzieć, jak wykryć, że kompilatorem jest GCC lub GCC emulujący GCC. Odpowiedź jest taka, że GCC definiuje __GNUC__ aby to wskazać.

Zobacz typowe atrybuty funkcji, aby uzyskać informacje o atrybutach - w szczególności atrybut format .

Przepisz 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

Jeśli popełnisz błąd, taki jak:

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

(gdzie %d powinno być %s ), wtedy kompilator będzie narzekał:

$ 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
$

Za pomocą ciągu formatu

Użycie łańcucha formatu zapewnia informacje o oczekiwanej liczbie i typach kolejnych argumentów variadic w taki sposób, aby uniknąć potrzeby jawnego argumentu count lub wartości terminatora.

Poniższy przykład pokazuje funkcję, która otacza standardową funkcję printf() , pozwalając jedynie na użycie różnych argumentów typu char , int i double (w formacie dziesiętnym zmiennoprzecinkowym). Tutaj, podobnie jak w przypadku printf() , pierwszym argumentem funkcji zawijania jest ciąg formatu. Podczas analizowania ciągu formatu funkcja może określić, czy oczekuje się innego argumentu variadic i jaki powinien być jego typ.

#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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow