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 :
int
.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 :
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.17.
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 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 les questions 13.5 et 9.10.
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.
printf() est une fonction à nombre variable de paramètres. Son prototype est le suivant :
int printf(const char * format, ...); /* C 90 */ int printf(const char * restrict format, ...); /* C 99 */
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).
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.
Pour les fonctions utilisant des paramètres variables du type printf(), GCC fournit une extension lors du prototypage pour que le compilateur fournisse des avertissements si les paramètres variables ne sont pas conformes au standard printf.
Voici un petit exemple :
#include <stdarg.h> extern void my_trace(int, const char *, ...) __attribute__ ((format (printf, 2, 3))); void my_trace(int type, const char *fmt, ...) { va_list argp; if (type) { va_start(argp, fmt); vfprintf(stderr, fmt, argp); va_end(argp); } }
L'attribut __attribute__
indique que la fonction utilise des arguments du type printf,
dont le format (chaîne avec %) se trouve en position 2, et les arguments du printf se trouvent à partir
de la position 3.
D'autres types que printf sont connus, comme scanf, strftime et strfmon. Voir la documentation de GCC sur http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html.
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; }
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.
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.
Un en-tête est un ensemble de déclarations, définitions et prototypes
nécessaires pour compiler et pour utiliser un module.
Par exemple, pour utiliser les fonctions d'entrées/sorties de la
bibliothèque standard, il est nécessaire d'inclure dans son programme
l'en-tête <stdio.h>
.
Par abus, on parle souvent de fichier d'en-tête, car historiquement, et encore aujourd'hui pour de nombreuses implémentations, ces en-têtes sont des fichiers. C'est également le cas pour les en-têtes personnels. Toutefois, la norme n'exige pas que les en-têtes standards soient des fichiers à proprement parlé.