Ricerca…


introduzione

Gli argomenti variabili sono usati dalle funzioni della famiglia printf ( printf , fprintf , ecc.) E altri per consentire a una funzione di essere chiamata ogni volta con un numero diverso di argomenti, da cui il nome varargs .

Per implementare le funzioni utilizzando la funzione di argomenti variabili, utilizzare #include <stdarg.h> .

Per chiamare le funzioni che accettano un numero variabile di argomenti, assicurarsi che esista un prototipo completo con i puntini di sospensione finali: void err_exit(const char *format, ...); per esempio.

Sintassi

  • void va_start (va_list ap, last ); / * Inizia l'elaborazione degli argomenti variadici; l'ultimo è l'ultimo parametro di funzione prima dei puntini di sospensione ("...") * /
  • digita va_arg (va_list ap, type ); / * Trova il prossimo argomento variadico nella lista; assicurati di passare il tipo corretto promosso * /
  • void va_end (va_list ap); / * Elaborazione argomento finale * /
  • void va_copy (va_list dst, va_list src); / * C99 o successivo: elenco degli argomenti della copia, ovvero la posizione corrente nell'elaborazione degli argomenti, in un'altra lista (ad esempio per passare più volte gli argomenti) * /

Parametri

Parametro Dettagli
va_list ap puntatore argomento, posizione corrente nell'elenco di argomenti variadici
scorso nome dell'ultimo argomento di funzione non variabile, quindi il compilatore trova il punto corretto per iniziare l'elaborazione degli argomenti variadici; potrebbe non essere dichiarato come variabile di register , funzione o tipo di matrice
genere tipo promosso dell'argomento variadic da leggere (es. int per un short int argomento short int )
va_list src puntatore argomento corrente da copiare
va_list dst nuovo elenco di argomenti da compilare

Osservazioni

Le va_start , va_arg , va_end e va_copy sono in realtà macro.

Assicurati di chiamare sempre va_start prima, e solo una volta, e di chiamare va_end ultimo, e solo una volta, e su ogni punto di uscita della funzione. Non farlo potrebbe funzionare sul tuo sistema ma sicuramente non è portabile e quindi invita bug.

Fare attenzione per dichiarare la funzione in modo corretto, vale a dire con un prototipo, e la mente le restrizioni l'ultimo argomento non variadic (non register , non è un tipo di funzione o array). Non è possibile dichiarare una funzione che accetta solo argomenti variadici, poiché è necessario almeno un argomento non-variadico per avviare l'elaborazione degli argomenti.

Quando si chiama va_arg , è necessario richiedere il tipo di argomento promosso , ovvero:

  • short è promosso a int (e unsigned short è anche promosso a int meno che sizeof(unsigned short) == sizeof(int) , nel qual caso viene promosso a unsigned int ).
  • float è promosso a double .
  • signed char è promosso a int ; unsigned char viene promosso a int meno che sizeof(unsigned char) == sizeof(int) , che raramente è il caso.
  • char è solitamente promosso a int .
  • I tipi C99 come uint8_t o int16_t sono promossi in modo simile.

L'elaborazione di argomenti variadici storici (cioè K & R) è dichiarata in <varargs.h> ma non dovrebbe essere utilizzata in quanto obsoleta. L'elaborazione standard degli argomenti variadici (quella descritta qui e dichiarata in <stdarg.h> ) è stata introdotta in C89; la macro va_copy stata introdotta in C99 ma fornita da molti compilatori precedenti.

Utilizzo di un argomento di conteggio esplicito per determinare la lunghezza della va_list

Con qualsiasi funzione variadica, la funzione deve sapere come interpretare la lista degli argomenti variabili. Con le funzioni printf() o scanf() , la stringa di formato indica alla funzione cosa aspettarsi.

La tecnica più semplice consiste nel passare un conteggio esplicito degli altri argomenti (che sono normalmente tutti dello stesso tipo). Ciò è dimostrato nella funzione variadica nel codice sottostante che calcola la somma di una serie di numeri interi, dove può esserci un numero qualsiasi di numeri interi ma quel conteggio è specificato come argomento prima dell'elenco di argomenti variabili.

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

Utilizzo dei valori del terminatore per determinare la fine di va_list

Con qualsiasi funzione variadica, la funzione deve sapere come interpretare la lista degli argomenti variabili. L'approccio "tradizionale" (esemplificato da printf ) è quello di specificare il numero di argomenti in anticipo. Tuttavia, questa non è sempre una buona idea:

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

A volte è più robusto aggiungere un terminatore esplicito, esemplificato dalla funzione execlp() POSIX. Ecco un'altra funzione per calcolare la somma di una serie di numeri 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));
}

Buoni valori terminatori:

  • intero (che dovrebbe essere tutto positivo o non negativo) - 0 o -1
  • tipi a virgola mobile - NAN
  • tipi di puntatore - NULL
  • tipi di enumeratore - qualche valore speciale

Implementazione di funzioni con un'interfaccia `` printf () `

Un uso comune di elenchi di argomenti a lunghezza variabile consiste nell'implementare funzioni che sono un sottile involucro attorno alla famiglia di funzioni printf() . Uno di questi esempi è un insieme di funzioni di segnalazione degli errori.

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

Questo è un esempio di nudo; tali pacchetti possono essere molto elaborati. Normalmente, i programmatori useranno errmsg() o warnmsg() , che usano verrmsg() . Se qualcuno esce con la necessità di fare di più, però, la funzione esposta verrmsg() sarà utile. Si potrebbe evitare di esporre fino a quando si ha la necessità di esso ( YAGNI - non sono gonna bisogno ), ma la necessità sarà sorgere alla fine (si sono gonna bisogno - YAGNI).

errmsg.c

Questo codice ha solo bisogno di inoltrare gli argomenti variadici alla funzione vfprintf() per l'output all'errore standard. Segnala anche il messaggio di errore di sistema corrispondente al numero di errore di sistema ( errno ) passato alle funzioni.

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

Utilizzando errmsg.h

Ora puoi usare quelle funzioni come segue:

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

Se le chiamate di sistema open() o read() falliscono, l'errore viene scritto sull'errore standard e il programma viene close() con il codice di uscita 1. Se la chiamata di sistema close() fallisce, l'errore viene semplicemente stampato come messaggio di avviso e il programma continua.

Verifica dell'uso corretto dei formati printf()

Se si utilizza GCC (il compilatore GNU C, che fa parte della raccolta del compilatore GNU) o si utilizza Clang, è possibile fare in modo che il compilatore verifichi che gli argomenti passati alle funzioni del messaggio di errore corrispondano a quanto printf() . Poiché non tutti i compilatori supportano l'estensione, devono essere compilati in modo condizionale, il che è un po 'complicato. Tuttavia, la protezione che dà vale la pena.

Per prima cosa, dobbiamo sapere come rilevare che il compilatore è GCC o Clang che emula GCC. La risposta è che GCC definisce __GNUC__ per indicare ciò.

Vedi gli attributi delle funzioni comuni per informazioni sugli attributi, in particolare l'attributo format .

Errmsg.h 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

Ora, se commetti un errore come:

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

(dove %d dovrebbe essere %s ), quindi il compilatore si lamenterà:

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

Utilizzando una stringa di formato

L'uso di una stringa di formato fornisce informazioni sul numero e sul tipo attesi degli argomenti variadici successivi in ​​modo tale da evitare la necessità di un argomento di conteggio esplicito o di un valore di terminatore.

L'esempio seguente mostra una funzione che avvolge la printf() standard printf() , consentendo solo l'uso di argomenti variadici del tipo char , int e double (nel formato decimale in virgola mobile). Qui, come con printf() , il primo argomento della funzione wrapping è la stringa di formato. Quando viene analizzata la stringa di formato, la funzione è in grado di determinare se esiste un altro argomento variadico previsto e quale dovrebbe essere il tipo.

#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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow