Recherche…


Introduction

Les arguments variables sont utilisés par les fonctions de la famille printf ( printf , fprintf , etc.) et d’autres pour permettre à une fonction d’être appelée avec un nombre différent d’arguments à chaque fois, d’où le nom varargs .

Pour implémenter des fonctions à l'aide de la fonctionnalité d'arguments variables, utilisez #include <stdarg.h> .

Pour appeler des fonctions qui prennent un nombre variable d'arguments, assurez-vous qu'il existe un prototype complet avec les points de suspension suivants dans la portée: void err_exit(const char *format, ...); par exemple.

Syntaxe

  • void va_start (va_list ap, dernier ); / * Démarrer le traitement des arguments variadiques; last est le dernier paramètre de la fonction avant les points de suspension ("...") * /
  • tapez va_arg (va_list ap, tapez ); / * Récupère le prochain argument variadic dans la liste; assurez-vous de passer le bon type promu * /
  • void va_end (va_list ap); / * Fin du traitement des arguments * /
  • void va_copy (va_list dst, va_list src); / * C99 ou plus récent: copie la liste des arguments, c.-à-d. La position actuelle dans le traitement des arguments, dans une autre liste (par exemple pour passer plusieurs fois les arguments) * /

Paramètres

Paramètre Détails
va_list ap pointeur d'argument, position actuelle dans la liste des arguments variadiques
dernier nom du dernier argument de la fonction non variadique, le compilateur trouve donc le bon endroit pour commencer à traiter les arguments variadiques; ne peut pas être déclaré comme une variable de register , une fonction ou un type de tableau
type type promu de l'argument variadic à lire (par exemple, int pour un argument short int )
va_list src pointeur d'argument courant à copier
va_list dst nouvelle liste d'arguments à remplir

Remarques

Les fonctions va_start , va_arg , va_end et va_copy sont en fait des macros.

Veillez à toujours appeler va_start premier, et une seule fois, et à appeler va_end dernier, et une seule fois, et à chaque point de sortie de la fonction. Ne pas le faire peut fonctionner sur votre système mais n'est sûrement pas portable et invite donc des bogues.

Prenez soin de déclarer votre fonction correctement, c'est-à-dire avec un prototype, et tenez compte des restrictions sur le dernier argument non-variadique (pas de register , pas de fonction ou de type tableau). Il n'est pas possible de déclarer une fonction qui ne prend que des arguments variadiques, car au moins un argument non-variadic est nécessaire pour pouvoir commencer le traitement des arguments.

Lorsque vous appelez va_arg , vous devez demander le type d'argument promu , à savoir:

  • short est promu en int (et unsigned short est également promu en int sauf si sizeof(unsigned short) == sizeof(int) , auquel cas il est promu unsigned int ).
  • float est promu à double .
  • signed char est promu dans int ; unsigned char est également promu dans int sauf si sizeof(unsigned char) == sizeof(int) , ce qui est rarement le cas.
  • char est généralement promu en int .
  • Les types C99 comme uint8_t ou int16_t sont également promus.

Le traitement des arguments variadiques historiques (K & R) est déclaré dans <varargs.h> mais ne doit pas être utilisé car il est obsolète. Le traitement standard des arguments variadiques (celui décrit ici et déclaré dans <stdarg.h> ) a été introduit dans C89; la macro va_copy été introduite dans C99 mais fournie par de nombreux compilateurs avant cela.

Utilisation d'un argument de décompte explicite pour déterminer la longueur de la va_list

Avec toute fonction variadique, la fonction doit savoir interpréter la liste des arguments variables. Avec les fonctions printf() ou scanf() , la chaîne de format indique à la fonction à quoi s’attendre.

La technique la plus simple consiste à transmettre un nombre explicite des autres arguments (qui sont normalement tous du même type). Ceci est démontré dans la fonction variadic dans le code ci-dessous qui calcule la somme d'une série d'entiers, où il peut y avoir un nombre quelconque d'entiers mais ce compte est spécifié en tant qu'argument avant la liste des arguments variables.

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

Utiliser des valeurs de terminateur pour déterminer la fin de va_list

Avec toute fonction variadique, la fonction doit savoir interpréter la liste des arguments variables. L'approche «traditionnelle» (illustrée par printf ) consiste à spécifier le nombre d'arguments en amont. Cependant, ce n'est pas toujours une bonne idée:

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

Parfois, il est plus robuste d'ajouter un terminateur explicite, illustré par la fonction execlp() POSIX. Voici une autre fonction pour calculer la somme d'une série de nombres 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));
}

Bonnes valeurs de terminateur:

  • entier (censé être tout positif ou non négatif) - 0 ou -1
  • types à virgule flottante - NAN
  • types de pointeurs - NULL
  • types d'énumérateur - une valeur spéciale

Implémenter des fonctions avec une interface semblable à `printf ()`

Une utilisation courante des listes d'arguments de longueur variable consiste à implémenter des fonctions qui constituent un wrapper mince autour de la famille de fonctions printf() . Un exemple est un ensemble de fonctions de rapport d’erreur.

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

Ceci est un exemple simple; de tels paquets peuvent être très complexes. Normalement, les programmeurs utiliseront soit errmsg() soit warnmsg() , qui utilisent eux-mêmes verrmsg() interne. Si quelqu'un a besoin d'en faire plus, alors la fonction verrmsg() exposée sera utile. Vous pouvez éviter d' exposer jusqu'à ce que vous avez besoin pour cela ( YAGNI - vous n'êtes pas en avoir besoin ), mais la nécessité lèveras à terme (vous en aurez besoin - YAGNI).

errmsg.c

Ce code n'a besoin que de transférer les arguments variadiques à la fonction vfprintf() pour générer une erreur standard. Il signale également le message d'erreur du système correspondant au numéro d'erreur du système ( errno ) transmis aux fonctions.

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

Utiliser errmsg.h

Vous pouvez maintenant utiliser ces fonctions comme suit:

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

Si les appels système open() ou read() échouent, l'erreur est écrite dans l'erreur standard et le programme se termine avec le code de sortie 1. Si l'appel système close() échoue, l'erreur est simplement imprimée en tant que message d'avertissement. le programme continue.

Vérification de l'utilisation correcte des formats printf()

Si vous utilisez GCC (le compilateur GNU C, qui fait partie de la collection de compilateurs GNU), ou utilisez Clang, vous pouvez demander au compilateur de vérifier que les arguments que vous transmettez aux fonctions de message d'erreur correspondent à ce qu'attend printf() . Comme tous les compilateurs ne prennent pas en charge l'extension, celle-ci doit être compilée de manière conditionnelle, ce qui est un peu délicat. Cependant, la protection qu’elle procure en vaut la peine.

Tout d'abord, nous devons savoir comment détecter que le compilateur est GCC ou Clang émulant GCC. La réponse est que GCC définit __GNUC__ pour indiquer cela.

Voir les attributs de fonction communs pour plus d'informations sur les attributs - en particulier l'attribut 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

Maintenant, si vous faites une erreur comme:

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

(où %d devrait être %s ), alors le compilateur se plaindra:

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

Utiliser une chaîne de format

L'utilisation d'une chaîne de format fournit des informations sur le nombre et le type attendus des arguments variadic ultérieurs de manière à éviter la nécessité d'un argument de décompte explicite ou d'une valeur de terminaison.

L'exemple ci-dessous montre une fonction qui encapsule la fonction standard printf() , ne permettant l'utilisation que d'arguments variadiques du type char , int et double (en format décimal à virgule flottante). Ici, comme avec printf() , le premier argument de la fonction wrapping est la chaîne de format. A mesure que la chaîne de formatage est analysée, la fonction est capable de déterminer s’il ya un autre argument variadique attendu et quel est son type.

#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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow