Bonjour ! J'espère que vous êtes prêt(e) pour une nouvelle leçon qui va solidifier votre compréhension de la récursivité.
Introduction
Dans la leçon précédente, nous avons écrit notre première fonction récursive, FACTORIELLE, et nous avons eu un premier aperçu de son exécution avec une visualisation en "descente" et "montée". C'était une manière intuitive de se représenter le flux des appels.
Aujourd'hui, nous allons formaliser cette idée en explorant un concept fondamental en informatique : la pile d'appels (ou call stack en anglais). L'objectif est de vous apprendre à tracer manuellement l'exécution d'une fonction récursive en utilisant ce modèle. Comprendre la pile d'appels est essentiel, car c'est exactement le mécanisme que l'ordinateur utilise pour gérer les appels de fonctions, et en particulier les appels récursifs. Cette compétence vous permettra de "penser comme l'ordinateur" et de déboguer des algorithmes bien plus complexes à l'avenir.
1. Qu'est-ce que la pile d'appels ?
Quand un programme exécute une fonction, il doit garder une trace de l'endroit où il se trouve. Si cette fonction en appelle une autre, il doit se souvenir où reprendre la première fonction une fois la seconde terminée. La pile d'appels est la structure de données que l'ordinateur utilise pour gérer cette information.
Imaginez une pile d'assiettes :
- Quand vous ajoutez une assiette, vous la posez sur le dessus.
- Quand vous retirez une assiette, vous prenez celle qui est sur le dessus.
C'est un principe LIFO (Last-In, First-Out), ou "Dernier entré, premier sorti". La pile d'appels fonctionne de la même manière avec les appels de fonctions.
La récursivité • Bibliothèque - Zeste de Savoir
L'article "La récursivité" de Zeste de Savoir explique très clairement ce concept. Lisez la section suivante pour comprendre comment cette "pile" invisible gère les appels de nos fonctions.
Lisez uniquement la sous-section intitulée "La pile d'appels". Concentrez-vous sur la définition de la pile, son rôle pour "s'y retrouver", et la manière dont les appels "s'empilent". Ne vous préoccupez pas encore de la suite sur la "récursion terminale".
Pour résumer, chaque fois qu'une fonction est appelée, l'ordinateur crée un "bloc d'informations" (appelé contexte d'exécution ou frame) et le place au sommet de la pile. Ce bloc contient :
- Les paramètres de la fonction (ex: la valeur de
n). - Les variables locales à la fonction.
- L'adresse de retour (l'endroit où le programme doit continuer après la fin de la fonction).
Quand la fonction se termine, son bloc est retiré du sommet de la pile, et le programme reprend son exécution là où il s'était arrêté dans la fonction précédente.
2. Le traçage visuel de la pile d'appels
Visualisons ce processus d'empilement (quand les fonctions s'appellent) et de dépilement (quand elles retournent un résultat).

Appliquons maintenant ce modèle à notre fonction FACTORIELLE(4) de la leçon précédente.
Fonction FACTORIELLE(n : Entier) : Entier
DEBUT
SI (n = 0) ALORS
RETOURNER 1
SINON
RETOURNER n * FACTORIELLE(n - 1)
FIN SI
FIN
Voici le déroulement sur la pile d'appels :

Voyons cela en détail :
-
Empilement :
FACTORIELLE(4)est appelé. Il est placé sur la pile. Pour calculer, il a besoin deFACTORIELLE(3). Il attend.FACTORIELLE(3)est appelé. Il est placé au-dessus. Il a besoin deFACTORIELLE(2). Il attend.FACTORIELLE(2)est appelé. Il est placé au-dessus. Il a besoin deFACTORIELLE(1). Il attend.FACTORIELLE(1)est appelé. Il est placé au-dessus. Il a besoin deFACTORIELLE(0). Il attend.FACTORIELLE(0)est appelé. Il est placé tout en haut de la pile. C'est le cas de base.
-
Dépilement :
FACTORIELLE(0)n'appelle personne. Il retourne1. Son bloc est retiré de la pile.FACTORIELLE(1)(maintenant au sommet) reçoit1. Il peut finir son calcul :1 * 1 = 1. Il retourne1. Son bloc est retiré.FACTORIELLE(2)(au sommet) reçoit1. Il calcule2 * 1 = 2. Il retourne2. Son bloc est retiré.FACTORIELLE(3)(au sommet) reçoit2. Il calcule3 * 2 = 6. Il retourne6. Son bloc est retiré.FACTORIELLE(4)(au sommet) reçoit6. Il calcule4 * 6 = 24. Il retourne24. Son bloc est retiré.
La pile est maintenant vide et le résultat final, 24, est obtenu.
3. Trace détaillée d'un autre exemple
Pour être sûr de bien maîtriser le mécanisme, analysons un autre exemple de manière très détaillée.
Récursion et pile - Le Tutoriel JavaScript Moderne
Le "Tutoriel JavaScript Moderne" propose une excellente analyse pas à pas d'une fonction récursive pow(x, n) qui calcule une puissance. Même si le code est en JavaScript, la logique de la pile d'appels est universelle. Cette lecture va renforcer votre modèle mental.
Lisez les sections depuis "Le contexte d’exécution et la pile" jusqu'à la fin de "La sortie". Suivez attentivement comment chaque appel à pow crée un nouveau contexte qui est empilé, puis comment les contextes sont dépilés un par un pour calculer le résultat final.
4. À vous de jouer : tracez une exécution
Votre préférence pour la pratique est une excellente habitude. Il est temps de l'appliquer. Prenez une feuille et un crayon. Voici la fonction SommeCarres de l'exercice de la leçon précédente :
Fonction SommeCarres(n : Entier) : Entier
DEBUT
SI (n = 0) ALORS
RETOURNER 0
SINON
RETOURNER n*n + SommeCarres(n - 1)
FIN SI
FIN
Votre tâche : Tracez manuellement l'exécution de SommeCarres(3) en dessinant l'état de la pile d'appels à chaque étape, comme nous l'avons fait pour FACTORIELLE. Identifiez clairement la phase d'empilement et la phase de dépilement, ainsi que les valeurs retournées à chaque étape.
...
Solution :
Voici comment la trace devrait se présenter :
Phase d'empilement :
SommeCarres(3)est appelé. Il est placé sur la pile. Il attend le résultat deSommeCarres(2).- Pile : [
SommeCarres(3)]
- Pile : [
SommeCarres(2)est appelé. Il est placé sur la pile. Il attend le résultat deSommeCarres(1).- Pile : [
SommeCarres(3),SommeCarres(2)]
- Pile : [
SommeCarres(1)est appelé. Il est placé sur la pile. Il attend le résultat deSommeCarres(0).- Pile : [
SommeCarres(3),SommeCarres(2),SommeCarres(1)]
- Pile : [
SommeCarres(0)est appelé. Il est placé sur la pile. C'est le cas de base.- Pile : [
SommeCarres(3),SommeCarres(2),SommeCarres(1),SommeCarres(0)]
- Pile : [
Phase de dépilement :
SommeCarres(0)retourne0. Son bloc est retiré.- Pile : [
SommeCarres(3),SommeCarres(2),SommeCarres(1)]
- Pile : [
SommeCarres(1)reçoit0. Il calcule1*1 + 0 = 1. Il retourne1. Son bloc est retiré.- Pile : [
SommeCarres(3),SommeCarres(2)]
- Pile : [
SommeCarres(2)reçoit1. Il calcule2*2 + 1 = 5. Il retourne5. Son bloc est retiré.- Pile : [
SommeCarres(3)]
- Pile : [
SommeCarres(3)reçoit5. Il calcule3*3 + 5 = 14. Il retourne14. Son bloc est retiré.- Pile : [vide]
Le résultat final est 14. Si votre trace ressemble à cela, bravo ! Vous avez parfaitement assimilé le mécanisme.
5. L'importance pratique : le risque de "Stack Overflow"
Pourquoi est-il si important de comprendre cette pile ? Parce qu'elle n'est pas infinie. Elle a une taille limitée en mémoire. Si une fonction récursive s'appelle elle-même un trop grand nombre de fois (par exemple, si vous oubliez le cas de base ou si le problème est trop grand), la pile va déborder. C'est ce qu'on appelle une erreur de dépassement de pile (stack overflow). C'est l'une des erreurs les plus classiques en récursivité.
La récursivité • Bibliothèque - Zeste de Savoir
L'article de Zeste de Savoir que vous avez consulté plus tôt aborde justement ce problème.
Dans la section "La pile d'appels", lisez le paragraphe qui commence par "En général ce n'est pas grave...". Il explique concrètement le risque de faire un appel comme rebours(1000000) et la conséquence : une erreur de dépassement de pile.
Comprendre la pile vous aide donc non seulement à suivre l'exécution, mais aussi à anticiper les limites de vos algorithmes récursifs.
Conclusion
Félicitations pour avoir terminé cette leçon dense ! Vous avez mis un nom et une structure sur l'intuition que nous avions développée.
Les points clés à retenir :
- La pile d'appels est la structure LIFO (Dernier entré, premier sorti) que l'ordinateur utilise pour gérer les appels de fonctions.
- Chaque appel de fonction empile un nouveau contexte d'exécution (paramètres, variables locales, adresse de retour).
- Lorsqu'une fonction se termine, son contexte est dépilé, et la valeur de retour est transmise à l'appelant.
- Tracer l'état de la pile est la méthode la plus rigoureuse pour suivre l'exécution d'un algorithme récursif.
- La taille limitée de la pile peut causer une erreur de dépassement de pile (stack overflow) si la récursion est trop profonde.
Et après ?
Maintenant que vous comprenez le fonctionnement interne de la récursivité, y compris son coût en mémoire lié à la pile, nous sommes équipés pour faire une comparaison éclairée. Dans la prochaine leçon, nous allons comparer les solutions récursives et itératives (avec des boucles) pour un même problème. Nous analyserons leurs avantages et inconvénients en termes de lisibilité, de performance et d'utilisation de la mémoire.