Sök…


Introduktion

Variabla argument används av funktioner i printf-familjen ( printf , fprintf , etc) och andra för att tillåta att en funktion kallas med ett annat antal argument varje gång, varav namnet varargs .

För att implementera funktioner med funktionen variabla argument använder du #include <stdarg.h> .

För att ringa funktioner som tar ett variabelt antal argument, se till att det finns en fullständig prototyp med efterföljande ellips i omfattning: void err_exit(const char *format, ...); till exempel.

Syntax

  • void va_start (va_list ap, last ); / * Starta bearbetning av varierande argument; sist är den sista funktionsparametern före ellipsen (“...”) * /
  • typ va_arg (va_list ap, typ ); / * Få nästa variadiska argument i listan; se till att du passerar rätt promoterad typ * /
  • void va_end (va_list ap); / * Slutargumentbehandling * /
  • void va_copy (va_list dst, va_list src); / * C99 eller senare: kopiera argumentlista, dvs aktuell position i argumentbehandling, till en annan lista (t.ex. för att överföra argument flera gånger) * /

parametrar

Parameter detaljer
va_list ap argumentpekaren, aktuell position i listan med variadiska argument
sista namnet på det sista icke-variadiska funktionsargumentet, så kompilatorn hittar rätt plats att börja bearbeta variadiska argument; kanske inte deklareras som en register , en funktion eller en array-typ
typ befordrad typ av det variadiska argumentet att läsa (t.ex. int för ett short int argument)
va_list src nuvarande argumentpekare att kopiera
va_list dst ny argumentlista som ska fyllas i

Anmärkningar

va_start , va_arg , va_end och va_copy är faktiskt makroer.

Var noga med att alltid ringa va_start först, och bara en gång, och att ringa va_end sist, och endast en gång, och på varje utgångspunkt för funktionen. Om du inte gör det kan det fungera på ditt system men är säkert inte bärbart och därmed bjuder in buggar.

Var noga med att förklara din funktion korrekt, dvs med en prototyp, och tänk på begränsningarna för det sista icke-variadiska argumentet (inte register , inte en funktion eller array-typ). Det är inte möjligt att förklara en funktion som endast tar variadiska argument, eftersom minst ett icke-variadiskt argument behövs för att kunna starta argumentbehandling.

När du ringer va_arg måste du begära den promoterade argumenttypen, det vill säga:

  • short marknadsförs till int (och unsigned short marknadsförs också till int inte sizeof(unsigned short) == sizeof(int) , i vilket fall det marknadsförs till unsigned int ).
  • float befordras till double .
  • signed char främjas till int ; unsigned char är också marknadsfört till int inte sizeof(unsigned char) == sizeof(int) , vilket sällan är fallet.
  • char är vanligtvis befordrad till int .
  • C99-typer som uint8_t eller int16_t marknadsförs på liknande sätt.

Historisk (dvs. K&R) variadisk argumentbehandling deklareras i <varargs.h> men bör inte användas eftersom den är föråldrad. Standardvaradisk argumentbehandling (den som beskrivs här och förklaras i <stdarg.h> ) infördes i C89; va_copy introducerades i C99 men tillhandahölls av många kompilatorer innan det.

Med hjälp av ett uttryckligt räkneargument för att bestämma va_listens längd

För alla variadfunktioner måste funktionen veta hur man tolkar listan med variabla argument. Med funktionerna printf() eller scanf() berättar formatsträngen funktionen vad de kan förvänta sig.

Den enklaste tekniken är att passera ett tydligt antal av de andra argumenten (som vanligtvis är samma typ). Detta demonstreras i variadfunktionen i koden nedan som beräknar summan av en serie heltal, där det kan finnas något antal heltal men det räknet anges som ett argument före variabelargumentlistan.

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

Använd terminatorvärden för att bestämma slutet på va_list

För alla variadfunktioner måste funktionen veta hur man tolkar listan med variabla argument. Den "traditionella" metoden (exemplifierad av printf ) är att ange antalet argument framför. Detta är dock inte alltid en bra idé:

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

Ibland är det mer robust att lägga till en tydlig terminator, exemplifierad med POSIX execlp() -funktionen. Här är en annan funktion för att beräkna summan av en serie med double siffror:

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

Bra terminatorvärden:

  • heltal (antas vara alla positiva eller icke-negativa) - 0 eller -1
  • flytande punkttyper - NAN
  • pekartyper - NULL
  • räknartyper - något specialvärde

Implementera funktioner med ett "printf ()" -liknande gränssnitt

En vanlig användning av argumentlistor med variabel längd är att implementera funktioner som är ett tunt omslag runt funktionsfamiljen printf() . Ett sådant exempel är en uppsättning felrapporteringsfunktioner.

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

Detta är ett exempel med bara ben; sådana paket kan vara mycket detaljerade. Normalt kommer programmerare att använda antingen errmsg() eller warnmsg() , som själva använder verrmsg() internt. Men om någon kommer att behöva göra mer, kommer den exponerade verrmsg() -funktionen att vara användbar. Du kan undvika att utsätta det tills du har ett behov av det ( YAGNI - du inte kommer att behöva det ), men behovet kommer att uppstå så småningom (du är gonna behöver det - YAGNI).

errmsg.c

Den här koden behöver bara vidarebefordra de variabiska argumenten till vfprintf() -funktionen för att mata ut till standardfel. Det rapporterar också systemfelmeddelandet som motsvarar systemfelnumret ( errno ) som skickas till funktionerna.

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

Med errmsg.h

Nu kan du använda dessa funktioner enligt följande:

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

Om antingen det open() eller read() systemsamtalet misslyckas skrivs felet till standardfel och programmet avslutas med utgångskod 1. Om det close() systemsamtalet misslyckas skrivs felet bara ut som ett varningsmeddelande, och programmet fortsätter.

Kontrollera korrekt användning av format för printf()

Om du använder GCC (GNU C Compiler, som är en del av GNU Compiler Collection), eller använder Clang, kan du låta kompilatorn kontrollera att argumenten du skickar till felmeddelandefunktionerna stämmer med vad printf() förväntar sig. Eftersom inte alla kompilatorer stöder tillägget måste det sammanställas villkorligt, vilket är lite fiddly. Men det skydd det ger är värt ansträngningen.

Först måste vi veta hur man upptäcker att kompilatorn är GCC eller Clang som emulerar GCC. Svaret är att GCC definierar __GNUC__ att indikera det.

Se vanliga funktionsattribut för information om attributen - särskilt format .

Omskrivna 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

Om du gör ett misstag som:

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

(där %d ska vara %s ), kommer kompilatorn att klaga:

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

Med hjälp av en formatsträng

Användning av en formatsträng tillhandahåller information om det förväntade antalet och typen av efterföljande variadiska argument på ett sådant sätt att man undviker behovet av ett uttryckligt räkne-argument eller ett terminatorvärde.

Exemplet nedan visar aa-funktion som slår in funktionen standard printf() , som endast möjliggör användning av variadiska argument av typ char , int och double (i decimal flytande punktformat). Här, liksom med printf() , är det första argumentet till inslagningsfunktionen formatsträngen. När formatsträngen är analyserad kan funktionen avgöra om det förväntas ett annat variadargument och vad det ska vara.

#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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow