web-dev-qa-db-fra.com

Comment gérer ou éviter un débordement de pile en C ++

En C++, un débordement de pile entraîne généralement un crash irrécupérable du programme. Pour les programmes qui doivent être vraiment robustes, c'est un comportement inacceptable, en particulier parce que la taille de la pile est limitée. Quelques questions sur la façon de gérer le problème.

  1. Existe-t-il un moyen d'empêcher le débordement de pile par une technique générale. (Une solution évolutive et robuste, qui comprend le traitement des bibliothèques externes consommant beaucoup de pile, etc.)

  2. Existe-t-il un moyen de gérer les débordements de pile au cas où ils se produiraient? De préférence, la pile est déroulée jusqu'à ce qu'il y ait un gestionnaire pour traiter ce genre de problème.

  3. Il existe des langues qui ont des threads avec des piles extensibles. Est-ce que quelque chose comme ça est possible en C++?

Tout autre commentaire utile sur la solution du comportement C++ serait apprécié.

29
Ralph Tandetzky

La gestion d'un débordement de pile n'est pas la bonne solution, mais vous devez vous assurer que votre programme ne déborde pas de la pile.

N'allouez pas de grandes variables sur la pile (où ce qui est "grand" dépend du programme). Assurez-vous que tout algorithme récursif se termine après une profondeur maximale connue. Si un algorithme récursif peut récurser un nombre inconnu de fois ou un grand nombre de fois, gérez vous-même la récursivité (en conservant votre propre pile allouée dynamiquement) ou transformez l'algorithme récursif en un algorithme itératif équivalent

Un programme qui doit être "vraiment robuste" n'utilisera pas de bibliothèques tierces ou externes qui "consomment beaucoup de pile".


Notez que certaines plates-formes notifient un programme lorsqu'un débordement de pile se produit et permettent au programme de gérer l'erreur. Sous Windows, par exemple, une exception est levée. Cette exception n'est pas une exception C++, cependant, c'est une exception asynchrone. Alors qu'une exception C++ ne peut être levée que par une instruction throw, une exception asynchrone peut être levée à tout moment pendant l'exécution d'un programme. Ceci est cependant attendu, car un débordement de pile peut survenir à tout moment: tout appel de fonction ou allocation de pile peut déborder de la pile.

Le problème est qu'un débordement de pile peut provoquer la levée d'une exception asynchrone même à partir de code qui ne devrait pas générer d'exceptions (par exemple, à partir de fonctions marquées noexcept ou throw() en C++). Donc, même si vous gérez cette exception d'une manière ou d'une autre, vous n'avez aucun moyen de savoir que votre programme est dans un état sûr. Par conséquent, la meilleure façon de gérer une exception asynchrone est de ne pas la gérer du tout(*). Si l'un est lancé, cela signifie que le programme contient un bogue.

D'autres plates-formes peuvent avoir des méthodes similaires pour "gérer" une erreur de débordement de pile, mais toutes ces méthodes sont susceptibles de souffrir du même problème: le code qui ne devrait pas provoquer d'erreur peut provoquer une erreur.

(*) Il existe quelques exceptions très rares.

23
James McNellis

Vous pouvez vous protéger contre les débordements de pile en utilisant de bonnes pratiques de programmation, comme:

  1. Soyez très prudent avec la récursivité, j'ai récemment vu un SO résultant d'une fonction CreateDirectory récursive mal écrite, si vous n'êtes pas sûr si votre code est 100% ok, alors ajoutez une variable de garde qui arrêtera l'exécution après N appels récursifs. Ou mieux encore, n'écrivez pas de fonctions récursives.
  2. Ne créez pas d'énormes tableaux sur la pile, cela pourrait être des tableaux cachés comme un très grand tableau en tant que champ de classe. C'est toujours mieux d'utiliser le vecteur.
  3. Soyez très prudent avec alloca, surtout s'il est mis dans une définition macro. J'ai vu de nombreux SO résultant de macros de conversion de chaînes placées dans des boucles qui utilisaient alloca pour des allocations de mémoire rapides.
  4. Assurez-vous que la taille de votre pile est optimale, c'est plus important dans les plates-formes intégrées. Si votre thread ne fait pas grand-chose, donnez-lui une petite pile, sinon utilisez-en plus. Je sais que la réservation ne doit prendre qu'une plage d'adresses - pas la mémoire physique.

ce sont les plus SO causes que j'ai vues ces dernières années.

Pour une recherche automatique SO, vous devriez pouvoir trouver des outils d'analyse de code statique.

7
marcinj

Le C++ est un langage puissant, et avec ce pouvoir vient la capacité de se tirer une balle dans le pied. Je ne connais aucun mécanisme portable pour détecter et corriger/abandonner lorsqu'un débordement de pile se produit. Une telle détection serait certainement spécifique à l'implémentation. Par exemple, g ++ fournit -fstack-protector pour vous aider à surveiller l'utilisation de votre pile.

En général, votre meilleur pari est d'être proactif en évitant les grandes variables basées sur la pile et prudent avec les appels récursifs.

2
Mark B

Re: piles extensibles. Vous pourriez vous donner plus d'espace de pile avec quelque chose comme ceci:

#include <iostream>

int main()
{
    int sp=0;

    // you probably want this a lot larger
    int *mystack = new int[64*1024];
    int *top = (mystack + 64*1024);

    // Save SP and set SP to our newly created
    // stack frame
    __asm__ ( 
        "mov %%esp,%%eax; mov %%ebx,%%esp":
        "=a"(sp)
        :"b"(top)
        :
        );
    std::cout << "sp=" << sp << std::endl;

    // call bad code here

    // restore old SP so we can return to OS
    __asm__(
        "mov %%eax,%%esp":
        :
        "a"(sp)
        :);

    std::cout << "Done." << std::endl;

    delete [] mystack;
    return 0;
}

C'est la syntaxe de l'assembleur de gcc.

2