Question

Il y a 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

Y a-t-il un moyen de pouvoir utiliser un try-catch Bloquer pour empêcher cette erreur de terminer mon programme (tout ce que je veux faire est de retourner -1)?

Si oui, quelle est la syntaxe pour cela?

Comment puis-je gérer autrement bad_alloc en C ++?

Était-ce utile?

La solution

Vous pouvez l'attraper comme toute autre exception:

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

Tout ce que vous pouvez faire utilement à partir de ce point dépend de vous, mais c'est certainement possible techniquement.

Autres conseils

En général, vous ne peux pas, et ne devrait pas essayer, pour répondre à cette erreur. bad_alloc Indique qu'une ressource ne peut pas être allouée car une mémoire n'est pas suffisante. Dans la plupart des scénarios, votre programme ne peut pas espérer y faire face, et se terminer bientôt est le seul comportement significatif.

Pire encore, les systèmes d'exploitation modernes sont souvent excessifs: sur de tels systèmes, malloc et new peut-il un pointeur valide même s'il ne reste plus de mémoire libre - std::bad_alloc ne sera jamais lancé ou n'est pas du moins un signe fiable d'épuisement de la mémoire. Au lieu de cela, tente de accéder La mémoire allouée entraînera alors un défaut de segmentation, qui n'est pas capable (vous pouvez manipuler Le signal de défaut de segmentation, mais vous ne pouvez pas reprendre le programme par la suite).

La seule chose que tu pouvais faire lors de la rattrapage std::bad_alloc est peut-être de enregistrer l'erreur et d'essayer de garantir une fin du programme sûr en libérant des ressources exceptionnelles (mais cela se fait automatiquement dans le cours normal du déroulement de la pile après le lancement de l'erreur si le programme utilise RAII de manière appropriée).

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

  1. L'application doit s'assurer qu'elle s'exécute sur un système qui ne surmonte pas la mémoire, c'est-à-dire qu'il signale l'échec lors de l'allocation plutôt que plus tard.
  2. L'application doit être capable de libérer de la mémoire immédiatement, sans aucune autre allocation accidentelle entre-temps.

Il est extrêmement rare que les applications aient le contrôle du point 1 - applications d'espace utilisateur jamais Faites, c'est un paramètre à l'échelle du système qui nécessite des autorisations racinaires pour changer.1

Ok, alors supposons que vous avez fixe le point 1. Ce que vous pouvez maintenant faire est par exemple utiliser un Cache LRU Pour certaines de vos données (probablement certains objets commerciaux particulièrement importants qui peuvent être régénérés ou rechargés à la demande). Ensuite, vous devez mettre la logique réelle qui peut échouer dans une fonction qui prend en charge la réessayer - en d'autres termes, si elle est abandonnée, vous pouvez simplement le relancer:

lru_cache<widget> widget_cache;

double perform_operation(int widget_id) {
    std::optional<widget> maybe_widget = widget_cache.find_by_id(widget_id);
    if (not maybe_widget) {
        maybe_widget = widget_cache.store(widget_id, load_widget_from_disk(widget_id));
    }
    return maybe_widget->frobnicate();
}

…

for (int num_attempts = 0; num_attempts < MAX_NUM_ATTEMPTS; ++num_attempts) {
    try {
        return perform_operation(widget_id);
    } catch (std::bad_alloc const&) {
        if (widget_cache.empty()) throw; // memory error elsewhere.
        widget_cache.remove_oldest();
    }
}

// Handle too many failed attempts here.

Mais même ici, en utilisant std::set_new_handler au lieu de manipuler std::bad_alloc offre le même avantage et serait beaucoup plus simple.


1 Si vous créez une application qui Est-ce que Point de contrôle 1, et vous lisez cette réponse, veuillez me tirer un e-mail, je suis vraiment curieux de savoir votre situation.

Quel est le comportement spécifié de la norme C ++ de new en C ++?

La notion habituelle est que si new L'opérateur ne peut pas allouer la mémoire dynamique de la taille demandée, puis il devrait lancer une exception de type std::bad_alloc.
Cependant, quelque chose de plus se passe avant même un bad_alloc L'exception est lancée:

C ++ 03 Section 3.7.4.1.3: dit

Une fonction d'allocation qui ne parvient pas à allouer le stockage peut invoquer le New_Handler actuellement installé (18.4.2.2), le cas échéant. [Remarque: une fonction d'allocation fournie par le programme peut obtenir l'adresse de la fonction New_Handler actuellement installée à l'aide de la fonction set_new_handler (18.4.2.3).] Si une fonction d'allocation a été déclarée avec une spécification d'exception vide (15.4), Throw (), échoue à Allouer le stockage, il doit renvoyer un pointeur nul. Toute autre fonction d'allocation qui ne parvient pas à allouer le stockage ne doit indiquer l'échec en jetant une exception de la classe std :: bad_alloc (18.4.2.1) ou une classe dérivée de std :: bad_alloc.

Considérez l'échantillon 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 (très probablement) ne sera pas en mesure d'allouer de l'espace à 100 000 000 entiers et à la fonction outOfMemHandler() s'appellera, et le programme abandonnera après publier un message d'erreur.

Comme vu ici le comportement par défaut de new L'opérateur lorsqu'il est incapable de répondre à une demande de mémoire, c'est appeler le new-handler fonction à plusieurs reprises jusqu'à ce qu'il puisse trouver suffisamment de mémoire ou qu'il n'y a 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 s'assurer que la prochaine allocation réussit, soit enregistrer un autre gestionnaire, soit enregistrer aucun gestionnaire, ou ne pas revenir (c'est-à-dire terminer le programme). S'il n'y a pas de nouveau gestionnaire et que l'allocation échoue, l'opérateur lancera une exception.

Quel est le new_handler et set_new_handler?

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

Quelque chose comme:

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

Le paramètre de set_new_handler est un pointeur vers l'opérateur de fonction new devrait appeler s'il ne peut pas allouer la mémoire demandée. Sa valeur de retour est un pointeur vers 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 hors des conditions de mémoire en C ++?

Compte tenu du comportement de newUn programme utilisateur bien conçu doit gérer hors des conditions de mémoire en fournissant un bon new_handlerqui fait l'un des éléments suivants:

Rendre plus de mémoire disponible: Cela peut permettre à la prochaine tentative d'allocation de mémoire à l'intérieur de l'opérateur de l'opérateur de New réussir. Une façon d'implémenter cela est d'allouer un grand bloc de mémoire au démarrage du programme, puis de le publier pour une utilisation dans le programme la première fois que le nouveau-manteau est invoqué.

Installez un nouveau mancheur différent: Si le nouveau-manteau actuel ne peut plus rendre la mémoire disponible, et qu'il y a un autre nouveau-manteau qui peut, alors le nouveau-manteau actuel peut installer l'autre nouveau-manteau à sa place (en appelant set_new_handler). La prochaine fois que l'opérateur nouvel appelle la fonction du nouvel handler, il sera récemment installé.

(Une variation sur ce thème consiste à modifier son propre comportement, donc la prochaine fois qu'il sera invoqué, il fait quelque chose de différent. Une façon d'y parvenir est de faire modifier le nouveau-mainloir statique, spécifique à l'espace de noms, ou Données globales qui affectent le comportement du nouveau manteau.)

Désinstaller le nouveau-manteau: Cela se fait en passant un pointeur nul vers set_new_handler. Sans nouveau-manteau installé, operator new Je vais lancer une exception ((convertible en) std::bad_alloc) Lorsque l'allocation de la mémoire est infructueuse.

Jeter une exception convertible en std::bad_alloc. De telles exceptions ne sont pas prises par operator new, mais se propagera au site originaire de la demande de mémoire.

Sans retour: En appelant abort ou exit.

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;
}

Je peux suggérer une solution plus simple (et encore plus rapide) pour cela. new L'opérateur retournerait null si la mémoire ne pouvait pas ê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!

Laissez votre programme FOO sortir 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 de coquille Cela appelle le programme réel. Étant donné que les espaces d'adresse sont séparés, l'état de votre programme de shell est toujours bien défini.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top