ProgrammationProgrammeur système

Qu'est-ce qu'un dépassement de pile (stack overflow) en langage C et comment peut-on le provoquer ? Quels sont les mécanismes de protection et les moyens de prévenir de telles situations en programmation ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Le dépassement de pile (stack overflow) est une situation dans laquelle un programme consomme plus de mémoire de pile que ce qui a été alloué par le système. Historiquement, la pile était utilisée pour stocker des variables locales, des adresses de retour et des données temporaires lors des appels de fonctions. Dans les premières implémentations de C, la pile était assez petite et non protégée contre les dépassements.

Le problème survient lorsque qu'une fonction ou une chaîne d'appels de fonctions utilise trop de variables locales ou appelle des fonctions récursives sans condition de sortie, provoquant ainsi l'écriture de données en dehors de la mémoire allouée à la pile, entraînant des erreurs, des pannes et des vulnérabilités.

La solution consiste à concevoir des fonctions économes en mémoire, à éviter les récursions profondes ou infinies, et à ne pas placer de grands objets sur la pile. Le système d'exploitation peut prévenir les débordements grâce à une protection par pages de garde (guard pages), mais le développeur doit écrire du code qui n'entraîne pas de dépassements.

Exemple de code provoquant un dépassement de pile en raison d'une récursion infinie :

void foo() { int arr[1000]; // Un grand tableau local ne fait qu'aggraver le problème foo(); // Appel récursif sans sortie } int main() { foo(); return 0; }

Caractéristiques clés :

  • La pile est limitée en taille ; son dépassement provoque une erreur ou un plantage.
  • Une récursion profonde ou incontrôlée est la principale cause de dépassement.
  • Contrôler l'utilisation de grandes données sur la pile est la responsabilité du développeur.

Questions pièges.

Que se passe-t-il si l'on déclare un très grand tableau local dans une fonction (par exemple, int arr[1000000]) ?

Réponse : Un grand tableau local peut immédiatement utiliser toute la pile. En fonction du système d'exploitation et du compilateur, cela entraînera un échec lors de l'exécution de la fonction ou même un plantage du programme.

Exemple de code :

void func() { int arr[1000000]; // Beaucoup de mémoire arr[0] = 1; }

La récursion entraîne-t-elle toujours un débordement de pile ?

Réponse : Non, la récursion est utile si elle est limitée en profondeur. Le dépassement se produit uniquement si la profondeur de la récursion est élevée ou si elle n'est pas limitée.

Peut-on placer de grands tableaux statiques à l'intérieur des fonctions pour économiser de la mémoire ?

Réponse : Non, les grands tableaux static à l'intérieur d'une fonction occupent quand même de la mémoire, mais déjà dans le segment des données statiques, et non sur la pile. Ce n'est pas toujours économique, surtout si une mémoire locale temporaire est requise.

Exemple de code :

void func() { static int arr[1000000]; // Pas sur la pile, mais la zone statique occupe de l'espace indéfiniment }

Erreurs courantes et anti-modèles

  • Placer de grands tableaux/structures sur la pile.
  • Utiliser une récursion incontrôlée.
  • Ignorer les vérifications de profondeur de récursion dans les fonctions.

Exemple de la vie quotidienne

Cas négatif

Un programmeur a implémenté un tri rapide via récursion pour trier un grand tableau, sans limiter la profondeur des appels et sans utiliser de conditions de sortie. Le code a conduit à un dépassement de pile lors du traitement de données réelles.

Avantages :

  • Implémentation récursive belle et concise.

Inconvénients :

  • Inaccessibilité réelle de l'algorithme sur de grandes données, pannes fréquentes.
  • Plantage du programme en production.

Cas positif

Un autre programmeur a utilisé une implémentation itérative avec sa propre petite pile dans le tas, a contrôlé la profondeur de la récursion et a alloué de grands tableaux temporaires via malloc.

Avantages :

  • Pas de dépassement de la pile même avec des entrées très grandes.
  • Le programme fonctionne toujours correctement.

Inconvénients :

  • Complexité légèrement accrue du code.
  • Nécessité de contrôler la libération de la mémoire en cas d'erreurs.