Ellipse

L’ellipse est une capacité du langage C qui permet d’écrire des fonctions avec un nombre variable d’arguments. Un exemple classique est la fonction printf, fonction d’écriture standard du C (en C++, on utilise plutôt cout). La déclaration de printf est la suivante :

int printf(const char *format, ...)

Noter l’ellipse, qui se traduit par trois points de suspension derrière le dernier argument (la dernière virgule est facultative). On peut alors appeler printf en précisant autant de variables que l’on veut (y compris zéro) derrière la chaîne de caractères :

printf("Bonjour.");
printf("La racine de %d est %lg.", 2, sqrt(2));
printf("Il est %d heures, %d minutes, %d secondes.",
          heure, min, sec);

Dans la chaîne format, on doit placer des codes spéciaux suivant le caractère %, pour indiquer les variables à afficher. Les principaux formats sont les suivants :

%d Variable int
%ld Variable longint
%g Variable float
%lg Variable double
%s Variable char* (chaîne de caractères)
%c Variable char

mais il en existe beaucoup d’autres ; pour écrire le caractère %, il suffit de le redoubler (%%). On arrive ainsi à des écritures assez absconses :

printf("Bénéfices\t= %d\nPourcentage\t=  %g%%\n\n",  benef, pourcent);

(et encore, on peut faire bien pire). Le plus grave, cependant, est l’absence de contrôle de types. Si dans l’écriture ci-dessus, la variable pourcent est de type double, alors qu’une float est attendue d’après la chaîne de format, cela conduira au mieux à une écriture erronée, au pire à une catastrophe plus ou moins amusante. Essayez par exemple ceci :

int i = 1;
printf("Badaboum %s", i);

Nous ne pouvons pas vous dire le résultat, cela dépend de l’état de la mémoire ; mais on peut obtenir un bien curieux listing de celle-ci.

Pour ces deux raisons, clarté et sécurité, printf est avantageusement remplacée par le flot de sortie cout, qui lui ne peut absolument pas produire d’erreur de ce type (voir chapitre 9).

La programmation des fonctions avec ellipse est un peu délicate. Il faut utiliser trois fonctions spéciales définies dans <stdarg.h>, nommées va_start, va_arg, et va_end. Elles utilisent un type spécial nommé va_list :

void va_start(va_list vl, derniere);
type va_arg(va_list vl, type);
void va_end(va_list vl);

Voici comment faire dans la pratique :

Voici à titre d’exemple une fonction qui calcule la moyenne d’une suite d’entiers, terminés par un zéro :

#include <stdarg.h>
				
float moyenne(int premier...)
{
     va_list vl;
     va_start(vl, premier);
     long somme = premier;
     int suivant, nombre = 1;
     while (suivant = va_arg(vl, int)) {
         somme += suivant;
         nombre++;
         }
     va_end(vl);
     return somme/float(nombre);
}

On notera que vous devez connaître les types et le nombre d’arguments, par une méthode personnelle (dans l’exemple, tous les arguments sont int et le dernier est nul) ; il n’y a aucun moyen de les déterminer autrement. En outre il ne faut pas oublier d’argument en cours de route, ni les appels à va_start et va_end. De tels oublis peuvent provoquer un plantage de l’ordinateur.

L’utilisation de fonctions avec ellipse est aussi dangereuse que leur programmation. Ainsi, si l’on écrit malencontreusement :

float f = moyenne(1, 2, 3);    // catastrophe  !!

au lieu de  :

float f = moyenne(1, 2, 3, 0);

le résultat risque d’être désagréable. De même si l’on écrit :

float f = moyenne(1, i, 2, 3, 0);

et que i est nul.

D’une façon générale, il vaut mieux éviter les fonctions avec ellipse lorsqu’elles ne s’imposent pas. Ici, l’ellipse de la fonction sera avantageusement remplacée par un pointeur sur une liste d’entiers, plus un paramètre entier indiquant le nombre d’éléments à prendre en compte, ce qui permet de mettre des zéros dans la liste :

float moyenne(unsigned nombre, int *liste)
{
     long somme = 0;
     for (int i = nombre; i > 0; i--)
         somme += *liste++;
     return somme/float(nombre);
}
Exercice 5.1

Écrire une version de moyenne avec ellipse ayant pour premier paramètre le nombre nombre d’entiers qui le suivent.

Voir solution

On notera que la conversion automatique des arguments interdit de récupérer des variables de type float avec va_arg (ils sont en fait au format double).

Exercice 5.2

Écrire une fonction équivalente à printf. On ne prendra en compte que les formats %d (int), %g (pour double, et non float, à cause de ce qui vient d’être dit), %c (char) et %s (char*). On tiendra compte du redoublement %% indiquant un simple caractère %. On suppose que l’on dispose de trois fonctions, ecritchar qui écrit un caractère unique, ecritint qui écrit un int, et ecritdouble qui écrit un double (les écrire avec le flot de sortie cout par exemple).

Voir solution

Terminons en notant que la fonction va_start nécessite un premier argument pour aller chercher les suivants, et qu’en conséquence les déclarations du genre :

void danger(...)

quoique permises, sont très dangereuses puisqu’il n’y a pas de moyen simple de retrouver les arguments.

D’une façon générale, l’ellipse est un outil dangereux et difficile à manier, qui sera avantageusement remplacé par les arguments par défaut dans la majorité des cas.

Précédent Précédent Sommaire Sommaire Suivant Suivant