Zoeken…


Invoering

Variabele argumenten worden gebruikt door functies in de printf-familie ( printf , fprintf , enz.) En anderen om een functie toe te staan met een ander aantal argumenten elke keer, vandaar de naam varargs .

Gebruik #include <stdarg.h> om functies te implementeren met de functie variabele argumenten.

Om functies aan te roepen die een variabel aantal argumenten aannemen, moet u ervoor zorgen dat er een volledig prototype is met de volgende ellips in scope: void err_exit(const char *format, ...); bijvoorbeeld.

Syntaxis

  • void va_start (va_list ap, laatste ); / * Start variadische argumentverwerking; laatste is de laatste functieparameter vóór de ellips ("...") * /
  • type va_arg (va_list ap, type ); / * Krijg volgende variadisch argument in lijst; zorg ervoor dat u het juiste gepromoveerde type * /
  • void va_end (va_list ap); / * Einde argumentverwerking * /
  • void va_copy (va_list dst, va_list src); / * C99 of hoger: kopieer argumentenlijst, dwz huidige positie in argumentverwerking, naar een andere lijst (bijv. Om argumenten meerdere keren over te slaan) * /

parameters

Parameter Details
va_list ap argument pointer, huidige positie in de lijst met variadische argumenten
laatste naam van het laatste niet-variadische functieargument, zodat de compiler de juiste plaats vindt om te beginnen met het verwerken van variadische argumenten; mag niet worden gedeclareerd als een register , een functie of een arraytype
type gepromoveerd type variadisch argument om te lezen (bijvoorbeeld int voor een short int argument)
va_list src huidige argumentwijzer om te kopiëren
va_list dst nieuwe argumentlijst die moet worden ingevuld

Opmerkingen

De va_start , va_arg , va_end en va_copy zijn eigenlijk macro's.

Zorg ervoor dat u altijd eerst va_start , en slechts eenmaal, en dat u va_end laatste en slechts eenmaal va_end , en op elk exitpunt van de functie. Als u dit niet doet, kan dit op uw systeem werken, maar het is zeker niet draagbaar en nodigt dus uit tot bugs.

Zorg ervoor dat u uw functie correct aangeeft, dwz met een prototype, en let op de beperkingen op het laatste niet-variadische argument (geen register , geen functie of arraytype). Het is niet mogelijk om een functie te declareren die alleen variadische argumenten gebruikt, omdat er ten minste één niet-variadisch argument nodig is om de argumentverwerking te kunnen starten.

Wanneer u va_arg , moet u het gepromoveerde argumenttype aanvragen, dat wil zeggen:

  • short wordt gepromoveerd tot int (en unsigned short wordt ook gepromoveerd tot int tenzij sizeof(unsigned short) == sizeof(int) , in welk geval het wordt gepromoveerd tot unsigned int ).
  • float wordt gepromoveerd tot double .
  • signed char wordt gepromoveerd tot int ; unsigned char wordt ook gepromoveerd tot int tenzij sizeof(unsigned char) == sizeof(int) , wat zelden het geval is.
  • char wordt meestal gepromoveerd tot int .
  • C99-typen zoals uint8_t of int16_t worden op dezelfde manier gepromoot.

Historische (dwz K&R) variadische argumentverwerking wordt gedeclareerd in <varargs.h> maar mag niet worden gebruikt omdat deze verouderd is. Standaard variadische argumentverwerking (die hier beschreven en verklaard in <stdarg.h> ) werd geïntroduceerd in C89; de va_copy macro werd geïntroduceerd in C99 maar werd daarvoor door veel compilers geleverd.

Gebruik een expliciet telargument om de lengte van de va_list te bepalen

Bij elke variadische functie moet de functie weten hoe de lijst met variabele argumenten moet worden geïnterpreteerd. Met de functies printf() of scanf() geeft de opmaakreeks de functie aan wat te verwachten.

De eenvoudigste techniek is om een expliciete telling van de andere argumenten (die normaal allemaal van hetzelfde type zijn) door te geven. Dit wordt aangetoond in de variadische functie in de onderstaande code die de som van een reeks gehele getallen berekent, waarbij er een willekeurig aantal gehele getallen kan zijn, maar dat aantal wordt opgegeven als een argument voorafgaand aan de lijst met variabele argumenten.

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

Terminatorwaarden gebruiken om het einde van va_list te bepalen

Bij elke variadische functie moet de functie weten hoe de lijst met variabele argumenten moet worden geïnterpreteerd. De "traditionele" benadering (geïllustreerd door printf ) is het vooraf opgeven van het aantal argumenten. Dit is echter niet altijd een goed idee:

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

Soms is het robuuster om een expliciete terminator toe te voegen, geïllustreerd door de functie POSIX execlp() . Hier is een andere functie om de som van een reeks double getallen te berekenen:

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

Goede terminatorwaarden:

  • geheel getal (verondersteld allemaal positief of niet-negatief te zijn) - 0 of -1
  • drijvende komma types - NAN
  • pointer types - NULL
  • typen tellers - enige speciale waarde

Implementeren van functies met een `printf ()` -achtige interface

Een veelgebruikt gebruik van argumentlijsten met variabele lengte is om functies te implementeren die een dunne wikkel zijn rond de printf() van functies. Een voorbeeld hiervan is een set functies voor foutrapportage.

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

Dit is een kaal voorbeeld; dergelijke pakketten kunnen veel ingewikkeld zijn. Normaal gesproken gebruiken programmeurs errmsg() of warnmsg() , die zelf verrmsg() intern gebruiken. Als iemand echter meer moet doen, is de zichtbare verrmsg() -functie nuttig. Je kon niet bloot te stellen totdat je een behoefte aan is ( YAGNI - u niet zult het nodig hebben ), maar de behoefte zal uiteindelijk ontstaan (je zult het nodig hebben - YAGNI).

errmsg.c

Deze code hoeft alleen de vfprintf() argumenten door te sturen naar de vfprintf() -functie voor uitvoer naar standaardfout. Het rapporteert ook het systeemfoutbericht dat overeenkomt met het systeemfoutnummer ( errno ) dat aan de functies is doorgegeven.

#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

Nu kunt u die functies als volgt gebruiken:

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

Als de open() of read() systeemaanroepen mislukken, wordt de fout naar standaardfout geschreven en wordt het programma afgesloten met exitcode 1. Als de systeemaanroep close() mislukt, wordt de fout alleen afgedrukt als een waarschuwingsbericht en het programma gaat verder.

Controle van het juiste gebruik van printf() formaten

Als u GCC gebruikt (de GNU C Compiler, die deel uitmaakt van de GNU Compiler Collection), of Clang gebruikt, kunt u de compiler laten controleren of de argumenten die u doorgeeft aan de foutberichtfuncties overeenkomen met wat printf() verwacht. Aangezien niet alle compilers de extensie ondersteunen, moet deze voorwaardelijk worden gecompileerd, wat een beetje lastig is. De bescherming die het biedt, is echter de moeite waard.

Eerst moeten we weten hoe we kunnen detecteren dat de compiler GCC is of Clang GCC emuleert. Het antwoord is dat GCC __GNUC__ definieert om dat aan te geven.

Zie gemeenschappelijke functie attributen voor meer informatie over de attributen - in het bijzonder de format attribuut.

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

Als u nu een fout maakt, zoals:

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

(waar de %d %s moet zijn), dan zal de compiler klagen:

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

Een opmaakreeks gebruiken

Het gebruik van een opmaakreeks geeft informatie over het verwachte aantal en type van de volgende variadische argumenten op een zodanige manier dat de noodzaak van een expliciet telargument of een terminatorwaarde wordt vermeden.

Het onderstaande voorbeeld toont een functie die de standaardfunctie printf() omhult, waarbij alleen variadische argumenten van het type char , int en double (in decimaal drijvend puntformaat). Hier, net als bij printf() , is het eerste argument voor de omloopfunctie de opmaakreeks. Aangezien de opmaakreeks wordt ontleed, kan de functie bepalen of er een ander variadisch argument wordt verwacht en wat het type moet zijn.

#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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow