Les fonctions et macrofonctions à nombre variable d'arguments

Dans le chapitre précédent, il a été question des pointeurs de fonction et, notamment, des pointeurs « génériques » de fonction. Néanmoins, ces derniers ne permettent pas d’expliquer le fonctionnement des fonctions printf() et scanf(). C’est ce que nous allons voir dans ce chapitre en étudiant les fonctions à nombre variable d’arguments. Nous en profiterons pour vous présenter par la suite les macrofonctions à nombre variable d’arguments.

Présentation

Une fonction à nombre variable d’arguments est, comme son nom l’indique, une fonction capable de recevoir et de manipuler un nombre variable d’arguments, mais en plus, et cela son nom ne le spécifie pas, potentiellement de types différents.

Une telle fonction se définit en employant comme dernier paramètre une suite de trois points appelée une ellipse indiquant que des arguments supplémentaires peuvent être transmis.

void affiche_suite(int n, ...);

Le prototype ci-dessus déclare une fonction recevant un int et, éventuellement, un nombre indéterminé d’autres arguments. La partie précédant l’ellipse décrit donc les paramètres attendus par la fonction et qui, comme pour n’importe quelle autre fonction, doivent lui être transmis.

Une fonction à nombre variable d’arguments doit toujours avoir au moins un paramètre non optionnel.

L’ellipse ne peut être placée qu’à la fin de la liste des paramètres.

Une fois définie, la fonction peut être appelée en lui fournissant zéro ou plusieurs arguments supplémentaires.

affiche_suite(10, 20);
affiche_suite(10, 20, 30, 40, 50, 60, 70, 80, 90, 100);
affiche_suite(10);

Comme pour les pointeurs génériques de fonction, le nombre et le types des arguments est inconnu du compilateur (c’est d’ailleurs bien le but de la manœuvre). Dès lors, les même règles de promotion s’appliquent ainsi que les problèmes qui y sont inhérents, notamment la problématique des pointeurs nuls. N’employer donc pas la macroconstante NULL pour fournir un pointeur nul comme argument optionnel.

L'en-tête <stdarg.h>

Une fois le prototype de la fonction déterminé, encore faut-il pouvoir manipuler ces arguments supplémentaires au sein de sa définition. Pour ce faire, la bibliothèque standard nous fourni trois macrofonctions : va_start(), va_arg() et va_end() définie dans l’en-tête <stdarg.h>. Ces trois macrofonctions attendent comme premier argument une variable de type va_list définit dans le même en-tête.

Afin d’illustrer leur fonctionnement, nous vous proposons directement un exemple mettant en œuvre une fonction affiche_suite() qui reçoit comme premier paramètre le nombre d’entiers qui vont lui être transmis et les affiche ensuite tous, un par ligne.

#include <stdarg.h>
#include <stdio.h>


void affiche_suite(int nb, ...)
{
    va_list ap;

    va_start(ap, nb);

    while (nb > 0)
    {
        int n;

        n = va_arg(ap, int);
        printf("%d.\n", n);
        --nb;
    }

    va_end(ap);
}


int main(void)
{
    affiche_suite(10, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100);
    return 0;
}
Résultat
10.
20.
30.
40.
50.
60.
70.
80.
90.
100.

La macrofonction va_start initialise le parcours des paramètres optionnels. Elle attend deux arguments : une variable de type va_list et le nom du dernier paramètre obligatoire de la fonction courante (dans notre cas nb). Il est impératif de l’appeler avant toute opération sur les paramètres optionnels.

La macrofonction va_arg() retourne le paramètre optionnel suivant en considérant celui-ci comme de type type. Elle attend deux arguments : une variable de type va_list précédemment initialisée par la macrofonction va_start() et le type du paramètre optionnel suivant.

Enfin, la fonction va_end() met fin au parcours des arguments optionnels. Elle doit toujours être appelée après un appel à la macrofonction va_start().

La macrofonction va_arg() n’effectue aucune vérification ! Cela signifie que si vous renseigner un type qui ne correspond pas au paramètre optionnel suivant ou si vous tentez de récupérer un paramètre optionnel qui n’existe pas, vous allez au-devant de comportements indéfinis.

Étant donné cet état de fait, il est impératif de pouvoir déterminer le nombre d’arguments optionnels envoyé à la fonction. Dans notre cas, nous avons opté pour l’emploi d’un paramètre obligatoire indiquant le nombre d’arguments optionnels envoyés.

Mais ?! Et si l’utilisateur de la fonction se plante en ne précisant pas le bon nombre ou le bon type d’arguments ?

Eh bien… C’est foutu. :-°
Il s’agit là d’une lacune similaire à celle des pointeurs « génériques » de fonction : étant donné que le type et le nombre des arguments optionnels est inconnu, le compilateur ne peut effectuer aucune vérification ou conversion.

Méthodes pour déterminer le nombre et le type des arguments

De manière générale, il existe trois grandes méthodes pour gérer le nombre et le type des arguments optionnels.

Les chaînes de formats

Cette méthode, vous la connaissez déjà, il s’agit de celle employée par les fonctions printf(), scanf() et consœurs. Elle consiste à décrire le nombre et le type des arguments à l’aide d’une chaîne de caractères comprenant des indicateurs.

Les suites d’arguments de même type

La seconde solution ne peut être utilisée que si tous les arguments optionnels sont de même type. Elle consiste soit à indiquer le nombre d’arguments transmis, soit à utiliser un délimitateur. La fonction affiche_suite() recourt par exemple à un paramètre pour déterminer le nombre de paramètres optionnels qu’elle a reçu.

Un délimitateur pourrait par exemple être un pointeur nul dans le cas d’une fonction similaire affichant une suite de chaîne de caractères.

#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>


static void affiche_suite(char *chaine, ...)
{
    va_list ap;

    va_start(ap, chaine);

    do
    {
        puts(chaine);
        chaine = va_arg(ap, char *);
    } while(chaine != NULL);

    va_end(ap);
}


int main(void)
{
    affiche_suite("un", "deux", "trois", (char *)0);
    return 0;
}
Résultat
un
deux
trois
Emploi d’un pivot

La dernière pratique consiste à recourir à un paramètre « pivot » qui fera varier le parcours des paramètres optionnels en fonction de sa valeur. Notez qu’un type énuméré se prête très bien pour ce genre de paramètre. Par exemple, la fonction suivante affiche un int ou un double suivant la valeur du paramètre type.

#include <stdarg.h>
#include <stdio.h>

enum type { TYPE_INT, TYPE_DOUBLE };


static void affiche(enum type type, ...)
{
    va_list ap;

    va_start(ap, type);

    switch (type)
    {
    case TYPE_INT:
        printf("Un entier : %d.\n", va_arg(ap, int));
        break;

    case TYPE_DOUBLE:
        printf("Un flottant : %f.\n", va_arg(ap, double));
        break;
    }

    va_end(ap);
}


int main(void)
{
    affiche(TYPE_INT, 10);
    affiche(TYPE_DOUBLE, 3.14);
    return 0;
}
Résultat
Un entier : 10.
Un flottant : 3.140000.

Les macrofonctions à nombre variable d'arguments

Vous vous en doutez en lisant le titre de cette section, il est également possible, à l’image des fonctions, de construire des macrofonctions à nombre variable d’arguments. Comme pour les fonctions, une macrofonction à nombre variable d’arguments se définit en indiquant une suite de trois points comme dernier argument.

Toutefois, à l’inverse des fonctions, la suite d’arguments variables ne peut être utilisée que d’un seul tenant à l’aide de la macroconstante __VA_ARGS__.

Ainsi, avec la définition ci-dessous, l’appel PRINTF("%d, %d, %d\n", 1, 2, 3) sera remplacé par printf(("%d, %d, %d\n"), 1, 2, 3).

#define PRINTF(fmt, ...) printf((fmt), __VA_ARGS__)

Notez qu’à l’inverse d’une fonction, une macrofonction ne requiert pas d’argument non optionnel. La définition suivante est donc parfaitement valide.

#define PRINTF(...) printf(__VA_ARGS__) /* Ok. */

En résumé
  1. Une fonction à nombre variable d’arguments est capable de recevoir et de manipuler une suite indéterminée d’arguments optionnels de types potentiellement différents.
  2. L’ellipse ne peut être placée qu’à la fin de la liste des paramètres et doit impérativement être précédée d’un paramètre non optionnel.
  3. La macrofonction va_arg() n’effectue aucune vérification, il est donc impératif de contrôler le nombre et le types des arguments reçus, par exemple à l’aide d’une chaîne de formats ou d’un paramètre décrivant le nombre d’arguments transmis.
  4. Il est possible de construire une macrofonction à nombre variable d’arguments en ajoutant une ellipse à la fin de sa liste d’argument.
  5. Les arguments optionnels sont utilisables dans la définition de la macrofonction en employant la macroconstante __VA_ARGS__.
  6. Une macrofonction ne requiert pas d’argument non optionnel.