Introduction aux fonctions en langage C
Les fonctions constituent le pilier de la programmation structurée en C. Elles permettent de réutiliser du code, de séparer les responsabilités et d'améliorer la lisibilité d'un programme. Ce cours reprend les concepts testés dans le quiz « Fonctions en langage C », en les développant de façon pédagogique et optimisée pour le référencement naturel (SEO). Vous y trouverez des définitions claires, des exemples concrets et des bonnes pratiques pour écrire, déclarer et compiler vos fonctions.
Déclaration vs définition d’une fonction
Qu’est‑ce qu’un prototype de fonction ?
En C, la déclaration (ou prototype) d’une fonction indique simplement son type de retour et la liste de ses paramètres, sans fournir le corps de la fonction. Elle ressemble à une promesse : « cette fonction existe, voici comment l’appeler ». Le prototype se place généralement dans un fichier d’en‑tête .h afin d’être partagé entre plusieurs fichiers source.
float calc_pol(float rayon);– prototype qui indique quecalc_polrenvoie unfloatet accepte unfloaten argument.- Le prototype ne contient pas d’accolades ni d’instructions de calcul.
Définition de la fonction
La définition fournit le corps complet de la fonction : les accolades, les variables locales, les instructions et éventuellement un return. C’est le moment où le compilateur sait exactement ce qui doit être exécuté.
float calc_pol(float rayon) { return 3.14159f * rayon * rayon; }- Le même nom que le prototype, mais avec le code réel à l’intérieur.
En résumé, la déclaration déclare l’existence, la définition définit le comportement. Confondre les deux conduit à des erreurs de lien (linker errors) ou à des prototypes incomplets.
Le mot‑clé void et les fonctions sans valeur de retour
Lorsque vous ne souhaitez pas que votre fonction renvoie une valeur, le type de retour doit être void. Ce mot‑clé indique au compilateur que la fonction ne produit aucun résultat exploitable par l’appelant.
void afficher_message(void) { printf("Bonjour\n"); }- Le paramètre
voidentre parenthèses signifie « aucun argument attendu ».
Utiliser void améliore la clarté du code : le lecteur comprend immédiatement que la fonction agit par effets de bord (affichage, modification de variables globales, etc.) et ne retourne rien.
Types de retour et prototypes dans les fichiers d’en‑tête
Le type de retour d’une fonction doit être cohérent entre le prototype et la définition. Dans le quiz, le prototype de calc_pol était déclaré comme float. Si vous changez le type dans le fichier .c (par exemple double), le compilateur générera une incohérence et le lien échouera.
Voici un exemple complet d’en‑tête et de source :
// calc_pol.h
#ifndef CALC_POL_H
#define CALC_POL_H
float calc_pol(float rayon); // prototype correct
#endif // CALC_POL_H
// calc_pol.c
#include "calc_pol.h"
#include
float calc_pol(float rayon) {
return M_PI * rayon * rayon; // corps de la fonction
}
Respecter le même type (float ici) évite les warnings et garantit que l’appelant reçoit la bonne précision.
Gestion des types d’arguments : conversion et perte d’information
Passer un argument de type int à une fonction qui attend un short peut entraîner une perte d’information. Le int est généralement 32 bits, alors que le short ne fait que 16 bits. La conversion implicite tronque les bits supérieurs, ce qui peut provoquer un overflow ou des valeurs inattendues.
Exemple :
void afficher_short(short s) {
printf("%d\n", s);
}
int main(void) {
int grand = 70000; // dépasse la capacité d'un short (32767)
afficher_short(grand); // affichera -5536 ou un autre résultat erroné
return 0;
}
Pour éviter ce problème, utilisez le même type dans le prototype et l’appel, ou effectuez une conversion explicite avec un cast tout en vérifiant les limites.
Passage de paramètres : valeur vs référence
En C, les paramètres sont transmis par valeur par défaut. Cela signifie que la fonction reçoit une copie de la variable d’appel. Modifier cette copie n’affecte pas la variable originale du main.
Dans le quiz, la fonction void mult2(int nb){ nb = nb*2; } ne change pas la variable du programme principal parce que nb est une copie locale. Pour modifier la variable d’origine, il faut passer son adresse (passage par référence) :
void mult2(int *p) {
*p = *p * 2; // modifie la valeur pointée
}
int main(void) {
int valeur = 5;
mult2(&valeur);
printf("%d\n", valeur); // affiche 10
return 0;
}
Cette technique est essentielle lorsqu’on veut que la fonction « renvoie » plusieurs résultats ou modifie directement des structures de données.
Syntaxe des fichiers d’en‑tête (.h)
Chaque déclaration de fonction dans un fichier .h doit se terminer par un point‑virgule ;. Oublier ce point‑virgule entraîne une erreur de syntaxe lors de la compilation du fichier source qui inclut l’en‑tête.
- Correct :
float calc_pol(float rayon); - Incorrect :
float calc_pol(float rayon)(manque le;)
Le point‑virgule indique la fin de la déclaration, exactement comme dans une variable globale.
L’instruction return : terminer une fonction
L’instruction return a deux rôles :
- Renvoie une valeur au code appelant (si la fonction n’est pas
void). - Interrompt immédiatement l’exécution de la fonction, même si du code reste après.
Dans l’exemple int calculer_aire(short l, short L){ int res = l*L; return res; }, le return res; est le seul moyen de transmettre le résultat au main. Le caractère } ne renvoie rien ; il ne fait que clôturer le bloc.
Un return placé trop tôt peut rendre du code inaccessible ; un return manquant dans une fonction non void conduit à un comportement indéfini.
Compilation avec gcc : ordre et bonnes pratiques
Lorsque votre projet comporte plusieurs fichiers source, gcc doit les recevoir tous avant de créer l’exécutable. La forme la plus courante est :
gcc prog_principal.c fonction.c -o mon_programme
L’option -o indique le nom du fichier exécutable. L’ordre des fichiers source n’est pas strictement important pour le compilateur, mais il est recommandé de placer le programme principal en premier pour plus de lisibilité.
Si vous avez plusieurs fonctions réparties dans plusieurs fichiers, vous pouvez aussi compiler séparément en objets puis les lier :
gcc -c prog_principal.c # crée prog_principal.o
gcc -c fonction.c # crée fonction.o
gcc prog_principal.o fonction.o -o mon_programme
Cette approche accélère les recompilations, car seuls les fichiers modifiés sont recompilés.
Conclusion
Maîtriser les fonctions en C passe par la compréhension de la différence entre déclaration et définition, le rôle du mot‑clé void, la cohérence des types de retour, la gestion des conversions de type, le passage de paramètres par valeur ou par référence, la syntaxe correcte des en‑têtes, l’utilisation de return et les bonnes pratiques de compilation avec gcc. En appliquant ces principes, vous écrirez du code plus sûr, plus lisible et plus facilement maintenable.
Pour aller plus loin, explorez les sujets suivants : fonctions inline, macros, gestion dynamique de la mémoire avec malloc, et utilisation des bibliothèques standard comme stdio.h et stdlib.h. Bonne programmation !