C : Fonctions variadiques et gestion des arguments variables

Apprenez à créer des fonctions variadiques en C avec va_list, valeurs terminatrices et format printf. Guide complet avec exemples pratiques.

Mahmoud DEVO
Mahmoud DEVO
December 28, 2025 5 min read
C : Fonctions variadiques et gestion des arguments variables

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 !".

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

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