Table of Contents
Utilisation de valeurs terminatrices pour déterminer la fin d’une liste de arguments variables
Introduction
Lorsque nous créons des fonctions qui prennent un nombre variable d’arguments, il est essentiel que la fonction puisse interpréter correctement cette liste d’arguments. L’approche « traditionnelle » (exemplifiée par printf) consiste à spécifier le nombre d’arguments en amont. Cependant, cela n’est pas toujours la bonne approche.
Exemple de mauvaise pratique
#include <stdarg.h>
extern int sum(int n, ...);
sum(5, 2, 1, 4, 3, 6)
Dans cet exemple, le premier argument (n) spécifie le nombre d’arguments suivants. Mais si nous supprimons un argument, la fonction ne sait plus quoi faire.
sum(5, 2, 1, 3, 6)
Cela peut entraîner des problèmes de sécurité ou même une panne du programme.
Utilisation d’une valeur terminatrice
Pour éviter ces problèmes, nous pouvons ajouter une valeur terminatrice explicite. L’exemple suivant utilise la fonction sum pour calculer la somme d’un nombre variable de nombres doubles :
#include <stdarg.h>
#include <stdio.h>
#include <math.h>
double sum(double x, ...) {
double somme = 0;
va_list va;
va_start(va, x);
for (; !isnan(x); x = va_arg(va, double)) {
somme += x;
}
va_end(va);
return somme;
}
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));
}
Dans cet exemple, la valeur terminatrice est NAN (Not à Number), qui est une valeur spéciale pour les nombres flottants.
Bonnes pratiques
- Utilisez des valeurs terminatrices explicitement pour éviter les problèmes de sécurité.
- Assurez-vous que les valeurs terminatrices soient correctement définies et utilisées.
- Documentez clairement la fonctionnalité de vos fonctions qui prennent un nombre variable d’arguments.
Common pitfalls
- N’oubliez pas de spécifier une valeur terminatrice explicite pour éviter les problèmes de sécurité.
- Assurez-vous que les valeurs terminatrices soient correctement définies et utilisées.
Implémentation de fonctions avec une interface printf-like
Introduction
L’une des utilisations courantes des listes d’arguments variables est la mise en œuvre de fonctions qui sont un simple masque autour des familles de fonctions printf.
Exemple
Considérons l’exemple suivant, qui définit une fonction pour écrire des messages d’erreur :
#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
Cette fonction est un masque autour de la fonction vfprintf, qui écrit le message d’erreur sur l’écran.
Implémentation
#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);
}
noreturn 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);
}
Utilisation
#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;
}
Dans cet exemple, si la fonction open ou read échoue, le message d’erreur est écrit sur l’écran et le programme sort avec un code de sortie de 1. Si la fonction close échoue, le message d’erreur est simplement écrit comme un message de warning et le programme continue.
Checking the correct use of printf() formats
Si vous utilisez GCC (le compilateur C GNU, qui fait partie de la Collection de Compilateurs GNU) ou Clang, vous pouvez demander au compilateur de vérifier que les arguments que vous passez aux fonctions de message d’erreur correspondent à ceux attendus par printf.
#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);
noreturn void errmsg(int exitcode, int errnum, const char *fmt, ...) PRINTFLIKE(3, 4);
void warnmsg(int errnum, const char *fmt, ...) PRINTFLIKE(2, 3);
#endif
Dans cet exemple, si vous faites une erreur comme celle-ci :
errmsg(EXIT_FAILURE, errno, "Failed to open file '%d' for reading", filename);
Le compilateur vous donnera un message d’erreur :
$ 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 $
Dans cet exemple, le compilateur vous avertit que la format "%d" attend un argument de type int, mais l’argument 4 (filename) est de type const char *.
Utilisation d’une chaîne de format
L’utilisation d’une chaîne de format fournit des informations sur le nombre et le type des arguments variables suivants, permettant ainsi d’éviter la nécessité d’un argument de comptage ou d’une valeur terminatrice.
Exemple
Considérons l’exemple suivant, qui définit une fonction pour imprimer les messages d’erreur :
#include <stdio.h>
#include <stdarg.h>
int simple_printf(const char *format, ...) {
va_list ap;
int printed = 0; /* count of printed characters */
va_start(ap, format);
while (*format != '\0') /* read format string until string terminator */
{
int f = 0;
if (*format == '%')
{
++format;
switch(*format)
{
case 'c':
f = printf("%c", va_arg(ap, int));
break;
default:
f = -1; /* unknown format character */
}
}
else
{
f = printf("%c", *format);
}
printed += f;
}
va_end(ap);
return printed;
}
Dans cet exemple, la fonction simple_printf lit la chaîne de format jusqu’à rencontrer un séparateur de chaînes (\0). Pour chaque caractère spécial (%) dans la chaîne, elle détermine le type d’argument suivant en fonction du caractère qui suit (c, s, etc.) et imprime l’argument correspondant.
Utilisation
int main(void) {
printf("Nombre de lettres : %d\n", simple_printf("Bonjour !"));
}
Dans cet exemple, la fonction simple_printf est utilisée pour imprimer le nombre de caractères dans la chaîne "Bonjour !".
In-Article Ad
Dev Mode
Tags
Mahmoud DEVO
Senior Full-Stack Developer
I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.
Enjoyed this article?
Subscribe to get more tech content delivered to your inbox.
Related Articles
Enums et Switch en C : Guide Complet des Bonnes Pratiques
Maitrisez les enumerations et instructions switch en C : declaration, valeurs explicites, flags bitwise et gestion exhaustive des cas.
Bit-fields et tableaux en C : guide pratique pour optimiser la memoire
Maitrisez les bit-fields et tableaux en C. Apprenez a creer des structures compactes, acceder aux elements et iterer efficacement sur vos donnees.
Header Guards en C : Prevention des Inclusions Multiples et Bonnes Pratiques
Maitrisez les header guards et pragma once en C : prevention des inclusions multiples, conventions de nommage et organisation des fichiers d'en-tete.