Faire une fonction avec un nombre d’arguments variable en C
Aujourd’hui, je vais vous parler fonctions variadiques, langage C et préprocesseur. Je vais vous expliquer ici comment coder une fonction en C avec un nombre d’arguments variable, un peu comme la fonction printf
. Dans cet article, je vais prendre comme exemple : la mise en place de fonction permettant l’enregistrement de log.
Une fonction à nombre d’arguments variable est appelée fonction variadique ou variadic function pour les anglophones. Les fonctions de ce type les plus connues sont printf
et scanf
mais vous pouvez bien sur écrire les vôtres.
Écritures d’une fonction variadique
L’écriture de ce genre de fonction est courante dans de nombreux langages, comme le Python, le Ruby ou encore le C++.
def printf(format, *args): print(format %args)
En C, l’utilisation de ce genre de fonction est très commune, mais l’écriture de ce genre de fonction l’est beaucoup moins. La fonction printf
est très souvent la première fonction utilisée lors de l’apprentissage du C via notamment la création du fameux HelloWorld
.
Le prototype d’une fonction variadique ressemble au suivant :
void log_print(int level, const char *format, ...);
Non, vous ne rêvez pas les ...
font parties du prototype de cette fonctions, ils permettent de dire que cette fonction reçoit un nombre indéterminé d’arguments.
La déclaration de la fonction est la suivante :
#include <stdarg.h> #include <syslog.h> void log_print(int level, const char *format, ...) { va_list args; va_start(args, format); openlog("my_app", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_USER); vsyslog(level, format, args); closelog(); va_end(args); }
La récupération des arguments variables se fait via les macros va_start
, va_arg
et va_end
. Dans l’exemple ci-dessus, nous n’utilisons pas va_arg
car vsyslog
accepte en paramètre une variable de type va_list
. ATTENTION, dans le cas ou vous donnez la variable args à une fonction, comme ici, vous ne devez pas l’utiliser ou la redonner sans la réinitialiser car l’état au retour de la fonction n’est pas garantie.
La fonction prise en exemple est une fonction de log qui peux être utilisée des façons suivantes :
log_print(LOG_DEBUG, "Hello World !"); log_print(LOG_DEBUG, "Hello World %s !", "toto"); log_print(LOG_DEBUG, "Hello World %s (%dème fois) !", "toto", 2);
Utilisation du préprocesseur
Dans le cadre de la mise en place de log, on va préférer utiliser des fonctions simples du type
DEBUG("Ceci est mon message de log");
à la place de
log_print(LOG_DEBUG, "Ceci est mon message de log");
Nous utiliserons les facilitées offertes par le préprocesseur en définissant un alias aux fonctions. Pour cela, il faut savoir 2 choses : la macro __VA_ARGS__
remplace les arguments dont le nombre n’est pas connu et la macro ##
stipule au préprocesseur qu’en cas d’absence d’argument, il devra supprimer la dernière virgule pour éviter les erreurs de compilation. Le résultat donne le code suivant :
#define DEBUG(format, ...) log_print(LOG_DEBUG, format, ##__VA_ARGS__) #define INFO(format, ...) log_print(LOG_INFO, format, ##__VA_ARGS__) #define WARN(format, ...) log_print(LOG_WARNING, format, ##__VA_ARGS__) #define ERROR(format, ...) log_print(LOG_ERR, format, ##__VA_ARGS__) void log_print(int level, const char *format, ...);
Voila vous avez maintenant des fonctions de log qui utilisent syslog
et qui sont facilement lisible et repérable.
Utilisation de va_arg
Dans l’exemple que je vous ai donné plus haut, je n’ai pas utilisé la macro va_arg
car la fonction vsyslog
accepte en paramètre un va_list
. Même si dans ce cas ça correspond à mon besoin, il faut de temps en temps utiliser la macros va_arg
. Le problème avec le fait d’avoir des arguments variadiques, c’est que vous ne connaissez ni le type ni le nombre de paramètres passés en argument. L’exemple ci-dessous est extrait de la page de man
#include <stdio.h> #include <stdarg.h> void foo(char *fmt, ...) { va_list ap; int d; char c, *s; va_start(ap, fmt); while (*fmt) switch (*fmt++) { case 's': /* string */ s = va_arg(ap, char *); printf("string %s\n", s); break; case 'd': /* int */ d = va_arg(ap, int); printf("int %d\n", d); break; case 'c': /* char */ /* need a cast here since va_arg only takes fully promoted types */ c = (char) va_arg(ap, int); printf("char %c\n", c); break; } va_end(ap); }
Cette exemple reprend le même fonctionnement que la fonction printf
sur la récupération des paramètres. Elle se base sur une chaîne de caractère modèle ftm. Lorsqu’elle rencontre un caractère s, d ou c va chercher dans les paramètres variadiques. Si vous mettez plus de caractères s,d ou c que vous n’avez de paramètres variadique, vous risquez de vouloir accéder à un espace mémoire non autorisé et de faire planter votre soft.
Voila vous avez maintenant connaissance de la possibilité d’utiliser les fonctions variadiques. N’hésitez pas à nous faire part de votre utilisation de celles-ci en commentaire.
Sources :
– Les pages de man de va_args, vsyslog, …
– La page consacrée aux variadiques de GCC