Faire une fonction avec un nombre d’arguments variable en C

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

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.