web-dev-qa-db-fra.com

Comment traiter avec bad_alloc en C ++?

Il existe une méthode appelée foo qui renvoie parfois l'erreur suivante:

terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Abort

Existe-t-il un moyen d'utiliser un bloc try-catch pour empêcher cette erreur de mettre fin à mon programme (tout ce que je veux faire est de retourner -1)?

Si oui, quelle est la syntaxe?

Sinon, comment puis-je traiter avec bad_alloc en C++?

48
Nosrettap

Vous pouvez l'attraper comme n'importe quelle autre exception:

try {
  foo();
}
catch (const std::bad_alloc&) {
  return -1;
}

Tout ce que vous pouvez faire utilement à partir de ce point appartient à vous, mais c'est techniquement réalisable.

31
Flexo

En général, vous ne pouvez pas et ne devez pas essayer de répondre à cette erreur. bad_alloc indique qu'une ressource ne peut pas être allouée car la mémoire disponible est insuffisante. Dans la plupart des scénarios, votre programme ne peut pas espérer y faire face, et mettre fin bientôt est le seul comportement significatif.

Pire encore, les systèmes d'exploitation modernes sur-allouent souvent: malloc et new renverront toujours un pointeur valide, même s'il ne reste techniquement pas (ou trop peu) de mémoire disponible - alors std::bad_alloc ne sera jamais jeté, ou du moins n’est pas un signe fiable d’épuisement de la mémoire. Au lieu de cela, les tentatives d’accès à la mémoire allouée entraîneront une erreur qui ne peut pas être capturée.

La seule chose que vous puissiez faire lorsque vous attrapez std::bad_alloc est peut-être de consigner l'erreur et d'essayer de terminer le programme en toute sécurité en libérant les ressources en suspens (mais cela se fait automatiquement au cours du déroulement normal de la pile après le déclenchement de l'erreur si le programme utilise correctement RAII).

Dans certains cas, le programme peut tenter de libérer de la mémoire et réessayer, ou utiliser une mémoire secondaire (= disque) au lieu de RAM), mais ces opportunités n'existent que dans des scénarios très spécifiques.

79
Konrad Rudolph

Quel est le comportement spécifié par C++ Standard de new en c ++?

La notion habituelle est que, si l'opérateur new ne peut pas allouer de mémoire dynamique de la taille demandée, il devrait alors émettre une exception de type std::bad_alloc.
Cependant, il se passe encore quelque chose avant même qu'une exception bad_alloc ne soit émise:

C++ 03 Section 3.7.4.1.3: dit

Une fonction d'allocation qui ne parvient pas à allouer de la mémoire peut appeler le new_handler actuellement installé (18.4.2.2), le cas échéant. [Remarque: une fonction d'allocation fournie par programme peut obtenir l'adresse du new_handler actuellement installé à l'aide de la fonction set_new_handler (18.4.2.3).] Si une fonction d'allocation déclarée avec une spécification d'exception vide (15.4), throw (), échoue allouer le stockage, il doit retourner un pointeur nul. Toute autre fonction d’allocation qui ne parvient pas à allouer de la mémoire ne doit indiquer cette défaillance qu’en levant une exception de classe std :: bad_alloc (18.4.2.1) ou une classe dérivée de std :: bad_alloc.

Prenons l'exemple de code suivant:

#include <iostream>
#include <cstdlib>

// function to call if operator new can't allocate enough memory or error arises
void outOfMemHandler()
{
    std::cerr << "Unable to satisfy request for memory\n";

    std::abort();
}

int main()
{
    //set the new_handler
    std::set_new_handler(outOfMemHandler);

    //Request huge memory size, that will cause ::operator new to fail
    int *pBigDataArray = new int[100000000L];

    return 0;
}

Dans l'exemple ci-dessus, operator new (le plus probable) ne pourra pas allouer d'espace pour 100 000 000 d'entiers, et la fonction outOfMemHandler() sera appelée et le programme cessera après émettant un message d'erreur.

Comme vu ici, le comportement par défaut de l'opérateur new lorsqu'il est impossible de répondre à une demande de mémoire, consiste à appeler la fonction new-handler jusqu'à ce qu'elle puisse trouver suffisamment de mémoire ou qu'il ne reste plus de nouveaux gestionnaires. Dans l'exemple ci-dessus, sauf si nous appelons std::abort(), outOfMemHandler() serait appelé à plusieurs reprises . Par conséquent, le gestionnaire doit soit s’assurer de la réussite de l’allocation suivante, soit enregistrer un autre gestionnaire, soit ne pas enregistrer de gestionnaire, soit ne pas renvoyer (c’est-à-dire terminer le programme). S'il n'y a pas de nouveau gestionnaire et que l'allocation échoue, l'opérateur lève une exception.

Que sont les new_handler et set_new_handler?

new_handler est un typedef pour un pointeur sur une fonction qui ne prend et ne retourne rien, et set_new_handler est une fonction qui prend et retourne un new_handler.

Quelque chose comme:

typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();

le paramètre set_new_handler est un pointeur sur l'opérateur de fonction que new doit appeler s'il ne peut pas allouer la mémoire demandée. Sa valeur de retour est un pointeur sur la fonction de gestionnaire précédemment enregistrée, ou null s'il n'y avait pas de gestionnaire précédent.

Comment gérer les conditions de mémoire insuffisante en C++?

Compte tenu du comportement de new, un programme utilisateur bien conçu devrait gérer les conditions de mémoire insuffisante en fournissant un new_handlerqui fait l'une des choses suivantes:

Rendre plus de mémoire disponible: Ceci peut permettre à la prochaine tentative d'allocation de mémoire dans la boucle de l'opérateur new de réussir. Une façon de le faire consiste à allouer un grand bloc de mémoire au démarrage du programme, puis à le relâcher pour l'utiliser dans le programme la première fois que le nouveau gestionnaire est appelé.

Installez un nouveau gestionnaire différent: Si le nouveau gestionnaire actuel ne peut pas rendre plus de mémoire disponible, et qu'un autre nouveau gestionnaire peut , le nouveau gestionnaire actuel peut alors installer l’autre nouveau gestionnaire à la place (en appelant set_new_handler). La prochaine fois que l'opérateur appelle la fonction new-handler, celui-ci sera installé le plus récemment.

(Une variante de ce thème permet à un nouveau gestionnaire de modifier son propre comportement. Ainsi, lors de son prochain appel, il fera quelque chose de différent. Pour ce faire, le nouveau gestionnaire modifiera les paramètres statique, spécifique à un espace de nom ou données globales qui affectent le comportement du nouveau gestionnaire.)

Désinstallez le nouveau gestionnaire: Pour cela, vous devez passer un pointeur null à set_new_handler. Si aucun nouveau gestionnaire n'est installé, operator new lève une exception ((convertible en) std::bad_alloc) lorsque l'allocation de mémoire échoue.

Lance une exception convertible en std::bad_alloc. Ces exceptions ne sont pas interceptées par operator new, mais se propagent au site à l'origine de la demande de mémoire.

Pas de retour: en appelant abort ou exit.

37
Alok Save

Je ne dirais pas cela, puisque bad_alloc _ signifie que vous êtes mémoire insuffisante. Il serait préférable d'abandonner au lieu d'essayer de récupérer. Cependant voici la solution que vous demandez:

try {
    foo();
} catch ( const std::bad_alloc& e ) {
    return -1;
}
8
Sam Miller

Je peux suggérer une solution plus simple (et même plus rapide) pour cela. new l'opérateur renvoie null si la mémoire ne peut être allouée.

int fv() {
    T* p = new (std::nothrow) T[1000000];
    if (!p) return -1;
    do_something(p);
    delete p;
    return 0;
}

J'espère que cela pourrait aider!

5
TrueY

Laissez votre programme foo quitter de manière contrôlée:

#include <stdlib.h>     /* exit, EXIT_FAILURE */

try {
    foo();
} catch (const std::bad_alloc&) {
    exit(EXIT_FAILURE);
}

Ensuite, écrivez un programme shell qui appelle le programme réel. Les espaces d’adresse étant séparés, l’état de votre programme Shell est toujours bien défini.

1
Wolf