ProgrammationDéveloppeur C++ Backend

Expliquez la différence entre le stack unwinding et la gestion des ressources lors du traitement des exceptions en C++. Comment garantir une libération correcte des ressources ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question :

C++ a été initialement conçu avec un accent sur la performance, donc la gestion des ressources (mémoire, fichiers, flux, sockets) se fait souvent manuellement. Lorsqu'une exception se produit, il est nécessaire de nettoyer les ressources acquises. Le stack unwinding (déroulement de la pile) est un mécanisme utilisé par C++ pour terminer correctement l'exécution des fonctions lors du lancement d'une exception.

Problème :

Lorsqu'une exception est lancée, le contrôle passe immédiatement aux blocs catch, et les fonctions intermédiaires sont "déroulées" : leurs destructeurs sont appelés, mais les appels explicites aux fonctions de libération peuvent être omis (par exemple, si des objets ne sont pas utilisés pour libérer automatiquement les ressources).

Solution :

En C++, la libération des ressources doit être confiée aux destructeurs, et non pas effectuée par des appels aux fonctions de libération manuellement. Le modèle RAII (Resource Acquisition Is Initialization) est un moyen explicite de rendre la libération de ressources automatique. Lors du déroulement de la pile, le destructeur sera appelé et la ressource sera libérée indépendamment du chemin de sortie de la fonction.

Exemple de code :

#include <fstream> #include <stdexcept> void readFile(const std::string& filename) { std::ifstream file(filename); // S'ouvrira et se fermera correctement même en cas d'exception if (!file.is_open()) { throw std::runtime_error("Le fichier ne peut pas être ouvert"); } // ... lire le fichier } // file se fermera même en cas d'exception

Caractéristiques clés :

  • Le stack unwinding est un mécanisme standard de destruction des objets lors du lancement d'une exception.
  • Libérez toujours les ressources dans les destructeurs.
  • Utilisez RAII ou des classes standard (par exemple, des pointeurs intelligents).

Questions pièges.

Si le code contient delete ptr; dans le bloc catch, est-ce suffisant pour libérer la mémoire ?

Non, si une exception se produit entre l'allocation de mémoire et le bloc catch, la mémoire peut ne pas être libérée. Il est préférable d'utiliser std::unique_ptr ou d'écrire delete dans le destructeur.

Exemple de code :

void foo() { int* data = new int[10]; // ... throw std::runtime_error("échec"); delete[] data; // ne sera pas appelé en cas d'exception }

Le stack unwinding peut-il sauter l'appel du destructeur pour un objet alloué sur la pile ?

Non, tous les objets locaux (non détruits jusqu'au point de lancement de l'exception) seront détruits dans l'ordre inverse de leur création, les destructeurs seront garantis d'être appelés.

Peut-on utiliser goto ou longjmp pour sortir d'un bloc try et s'attendre à ce que les destructeurs soient appelés ?

Non. C++ garantit l'appel des destructeurs uniquement lors du déroulement de la pile dû à une exception, et non à cause d'une gestion incorrecte du flux (goto, setjmp/longjmp).

Erreurs typiques et anti-modèles

  • Nettoyer les ressources manuellement à l'intérieur de try-catch, ignorant les destructeurs.
  • Utiliser des pointeurs bruts au lieu de RAII ou de classes standard.
  • Abstraire le traitement des exceptions de manière à ce que les ressources ne soient pas libérées (par exemple, setjmp/longjmp).

Exemple de la vie réelle

Cas négatif

Un programmeur alloue de la mémoire avec new, traite des exceptions en libérant la mémoire dans le bloc catch, mais oublie d'autres chemins de sortie de la fonction.

Avantages :

  • Au début, cela semble simple et "transparent"

Inconvénients :

  • Si une exception est lancée là où elle n'est pas attendue, une fuite de mémoire se produit.
  • Difficile à tester et à maintenir.

Cas positif

On utilise std::unique_ptr et des classes RAII pour toutes les ressources, la libération ne dépend pas de try/catch.

Avantages :

  • Pas de fuites de ressources.
  • La logique de gestion des erreurs devient plus simple.

Inconvénients :

  • Nécessite une meilleure compréhension de la bibliothèque standard et des idiomes du langage.