précédent  index  suivant

9. Fonctions et prototypes


9.1 Pour commencer ...

Il y a trois notions : La déclaration d'une fonction, c'est annoncer que tel identificateur correspond à une fonction, qui renvoie tel type. La définition d'une fonction est une déclaration où, en plus, on donne le code de la fonction elle-même. Le prototype est une déclaration de fonction où le type des arguments est également donné.

Par exemple :

    int f();    /* declaration de f(), renvoyant un int, pas de prototype  */

    int f(void);/* declaration de f(), renvoyant un int, prototype (0 arg) */

    int f(void) /* definition de f() avec declaration avec prototype       */
    {
        return 42;
    }

    int f(x)    /* definition de f() avec declaration sans prototype       */
    int x;
    {
        return x;
    }

    int f()     /* definition de f() avec declaration sans prototype       */
    {
        return 42;
    }
    

Ce qui n'est pas possible :

Ce qui est autorisé : Ce qui était autorisé en C90 mais ne l'est plus en C99 : Ce qui est encore autorisé en C99 mais disparaîtra bientôt : Ce qu'il faut faire quand on veut programmer lisiblement, en détectant les bugs et en gardant du code maintenable et compatible avec le futur :

haut de page

9.2 Qu'est-ce qu'un prototype ?

Un prototype est une signature de fonction. Comme tout objet en C, une fonction doit être déclarée avant son utilisation. Cette déclaration est le prototype de la fonction. Le prototype doit indiquer au compilateur le nom de la fonction, le type de la valeur de retour et le type des paramètres (sauf pour les fonctions à arguments variables, comme printf(). (cf. 9.6).

    int fa(int a, char const * const b);
    int fb(int, char const * const);
    

Les noms de paramètre sont optionnels, mais il est fortement conseillé de les laisser. Cela donne une bonne indication sur leurs rôles.

Les fonctions de la bibliothèque ont également leur prototype. Avant l'utilisation de celles-ci, il faut inclure les fichiers d'en-tête contenant les prototypes. Par exemple, le prototype de malloc() se trouve dans stdlib.h.

Certains préfèrent ajouter le mot clé extern au prototype, afin de rester cohérent avec la déclaration des variables globales.

Voir aussi les questions 9.3, 12.1, 13.5 et 14.16.

haut de page

9.3 Où déclarer les prototypes ?

Un prototype de fonction doit être déclaré avant l'utilisation de la fonction. Pour une plus grande lisibilité, mais aussi pour simplifier la maintenance du code, il est conseillé de regrouper tous les prototypes d'un module (fichier xxx.c) dans un fichier d'en-tête (xxx.h). Ce dernier n'a plus alors qu'à être inclus dans le code qui utilise ces fonctions. C'est le cas des fonctions de la bibliothèque standard.

Voir aussi la question 13.5.

haut de page

9.4 Quels sont les prototypes valides de main() ?

La fonction main() renvoie toujours un int. Les prototypes valides sont :

    int main(void);
    int main(int argc, char * argv[]);
    

Tout autre prototype n'est pas du tout portable et ne doit jamais être utilisé (même s'il est accepté par votre compilateur).

En particulier, vous ne devez pas terminer la fontion main() sans retourner une valeur positive (non nulle en cas d'erreur). Les valeurs de retour peuvent être 0, EXIT_SUCCESS ou EXIT_FAILURE.

On pourra aussi rencontrer (sous Unix) le prototype suivant :

    int main (int argc, char* argv[], char** arge);
    

dans le but d'utiliser les variables d'environnement du shell actif. Ce n'est ni portable ni standard, d'autant plus que les fonctions getenv(), setenv() et putenv() le sont et suffisent largement.

Enfin, rappelons que le prototype suivant

    int main () ;
    

est parfaitement valide en C++ (et est synonyme du premier présenté ici), mais ne l'est pas en C.

haut de page

9.5 Comment printf() peut recevoir différents types d'arguments ?

printf() est une fonction à nombre variable de paramètres. Son prototype est le suivant :

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

Le type et le nombre des paramètres n'est pas défini dans le prototype, c'est le traitement effectué dans la fonction qui doit les vérifier.

Pour utiliser cette fonction, il est donc impératif d'inclure l'en-tête <stdio.h>.

Pour écrire une fonction de ce type, lire la question suivante (9.6).

haut de page

9.6 Comment écrire une fonction à un nombre variable de paramètres ?

La bibliothèque standard fournit des outils pour faciliter la gestion de ce type de fonctions. On les trouve dans l'en-tête <stdarg.h>.

Le prototype d'une fonction à nombre variable de paramètres doit contenir au moins un paramètre explicite, puis se termine par ... Exemple :

    int f(int nombre, ...);
    

Il faut, d'une façon ou d'une autre, passer dans les paramètres le nombre d'arguments réellement transmis. On peut le faire en donnant ce nombre explicitement (comme printf()), ou passer la valeur NULL en dernier.

Attention toutefois avec la valeur NULL dans ce cas. En effet, NULL n'est pas nécessairement une valeur du type pointeur mais une valeur qui donne un pointeur nul si elle est affectée ou passée ou comparée à un type pointeur. Le passage d'une valeur à un paramètre n'est pas une affectation à un pointeur mais une affectation qui obéit aux lois spéciales pour les paramètres à nombre variable (ou pour les paramètres d'une fonction sans prototype). Les lois de promotion pour les types arithmétiques sont appliquées). Si NULL est défini par

#define NULL 0

alors (int)0 est passé à la fonction. Si un pointeur n'a pas la même taille qu'un int ou si un pointeur nul n'est pas représenté par « tous les bits 0 » le passage d'un 0 ne passe donc pas de pointeur nul. La méthode portable est donc

    f(toto,titi,(void*)NULL);
    

ou

	f(toto,titi,(void*)0);
    

C'est le seul cas où il faut caster NULL parce qu'il ne s'agit pas d'un contexte syntactique « de pointeur », seulement d'un contexte « de pointeur par contrat ».

Après cela, les fonctions va_start(), va_arg() et va_end() permettent de parcourir la liste des paramètres.

Voici un petit exemple :

    #include <stdarg.h>

    int vexemple(int nombre, ...){
        va_list argp;
        int i;
        int total = O;

        if(nombre < 1)
                return 0;

        va_start(argp, nombre);
        for (i = 0; i < nombre; i++) {
            total += va_arg(argp, int);
        }
        va_end(argp);

        return total;
    }
    

Merci à Horst Kraemer pour ces remarques.

haut de page

9.7 Comment modifier la valeur des paramètres d'une fonction ?

En C, les paramètres sont passés par valeur. Dans la plupart des implémentations, cela se fait par une copie dans la pile. Lors du retour de la fonction, ces valeurs sont simplement dépilées, et les modifications éventuelles sont perdues. Pour pallier cela, il faut simuler un passage des paramètres par référence, en passant un pointeur sur les variables à modifier. Voici l'exemple classique de l'échange des valeurs entre deux entiers :

    void echange(int * a, int * b) {
        int tmp = *a;
        *a = *b;
        *b = tmp;
    }
    

haut de page

9.8 Comment retourner plusieurs valeurs ?

Le langage C ne permet pas aux fonctions de renvoyer plusieurs objets. Une solution consiste à passer l'adresse des objets à modifier en paramètre. Une autre solution consiste à renvoyer une structure, ou un pointeur sur une structure qui contient l'ensemble des valeurs. Généralement, quand on a ce genre de choses à faire, c'est qu'il se cache une structure de données que l'on n'a pas identifiée. La pire des solutions est d'utiliser des variables globales.

haut de page

9.9 Peut-on, en C, imbriquer des fonctions ?

Non, on ne peut pas. Les concepteurs ont jugé cela trop compliqué à mettre en oeuvre (portée des variables, gestion de la pile etc.). Certaines implémentations, comme GNU CC le supportent toutefois. Ceci dit, on peut très bien s'en passer, en utilisant des pointeurs sur les structures de données à partager, ou en utilisant des pointeurs de fonctions.

haut de page

précédent  index  suivant

faq-fclc 5/3/2002 (8h 59:04)