web-dev-qa-db-fra.com

Est-ce que l'opérateur `new` du C++ peut jamais lancer une exception dans la vie réelle?

L'opérateur new peut-il créer une exception dans la vie réelle?

Et si oui, est-ce que j'ai d'autres options pour gérer une telle exception en plus de tuer ma demande?

Mettre à jour:

Est-ce que des applications new- heavy du monde réel vérifient les échecs et récupèrent lorsqu'il n'y a pas de mémoire?


Voir également:

40
osgx

L'opérateur new, et new [], devrait lancer std::bad_alloc, mais ce n'est pas toujours le cas car le comportement peut parfois être remplacé. 

On peut utiliser std::set_new_handler et tout à coup, il peut arriver quelque chose de tout à fait différent que de jeter std::bad_alloc. Bien que la norme exige que l’utilisateur rend la mémoire disponible, abandonne ou jette std::bad_alloc. Mais bien sûr, cela peut ne pas être le cas.

Disclaimer: Je ne suggère pas de faire cela.

23
Brian R. Bondy

Oui, new peut et va lancer si l’attribution échoue. Cela peut arriver si vous manquez de mémoire ou si vous essayez d'allouer un bloc de mémoire trop volumineux.

Vous pouvez intercepter l'exception std::bad_alloc et la gérer de manière appropriée. Parfois, cela a du sens, d'autres fois (lire: la plupart du temps), ce n'est pas le cas. Si, par exemple, vous essayez d’allouer un tampon énorme mais que vous pouvez travailler avec moins d’espace, vous pouvez essayer d’allouer successivement des blocs plus petits. 

37
James McNellis

Si vous utilisez un processeur standard embarqué sous Linux sans mémoire virtuelle, il est fort probable que le système d’exploitation mettra fin à votre processus avant que la nouvelle échoue si vous allouez trop de mémoire.

Si vous exécutez votre programme sur une machine disposant de moins de mémoire physique que le maximum de mémoire virtuelle (2 Go sous Windows standard), vous constaterez qu'une fois que vous aurez alloué une quantité de mémoire approximativement égale à la mémoire physique disponible, d'autres allocations aboutiront. mais causera la pagination sur le disque. Cela enlèvera votre programme et vous risquez de ne pas être en mesure d’épuiser votre mémoire virtuelle. Donc, vous pourriez ne pas avoir une exception levée.

Si vous avez plus de mémoire physique que la mémoire virtuelle et que vous continuez simplement à allouer de la mémoire, vous obtiendrez une exception lorsque vous aurez épuisé la mémoire virtuelle au point où vous ne pourrez plus allouer la taille de bloc demandée.

Si vous avez un programme de longue durée qui alloue et libère dans de nombreuses tailles de blocs différentes, y compris des petits blocs, avec une grande variété de durées de vie, la mémoire virtuelle peut devenir fragmentée au point que les nouveaux ne seront pas en mesure de trouver un bloc assez grand pour satisfaire une demande. Ensuite, new lancera une exception. Si vous rencontrez une fuite de mémoire qui provoque la fuite occasionnelle d'un petit bloc dans un emplacement aléatoire, elle finira par fragmenter la mémoire au point qu'une allocation de bloc arbitrairement petite échouera et qu'une exception sera levée.

Si vous avez une erreur de programme qui passe accidentellement une taille de tableau énorme à new [], new échouera et lèvera une exception. Cela peut arriver, par exemple, si la taille du tableau est en fait une sorte de motif d'octet aléatoire, peut-être dérivé de la mémoire non initialisée ou d'un flux de communication corrompu.

Tout ce qui précède est pour le nouveau global par défaut. Cependant, vous pouvez remplacer global new et vous pouvez fournir de nouvelles spécifiques à la classe. Celles-ci peuvent également être lancées et la signification de cette situation dépend de la façon dont vous l'avez programmée. il est habituel pour new d'inclure une boucle qui tente tous les moyens possibles pour obtenir la mémoire demandée. Il jette quand tous ceux sont épuisés. Ce que vous faites alors est à vous.

Vous pouvez intercepter une exception à partir de new et utiliser l'opportunité qu'il offre pour documenter l'état du programme à peu près au moment de l'exception. Vous pouvez "vider le noyau". Si vous avez un tampon d'instrumentation circulaire alloué au démarrage du programme, vous pouvez le vider sur disque avant de terminer le programme. La fin du programme peut être gracieuse, ce qui constitue un avantage par rapport à la non-gestion de l'exception. 

Personnellement, je n'ai pas vu d'exemple où de la mémoire supplémentaire pourrait être obtenue après l'exception. Une possibilité est cependant la suivante. Supposons que vous ayez un allocateur de mémoire très efficace, mais qui ne récupère pas bien l’espace libre. Par exemple, il peut être sujet à la fragmentation d’espace libre, dans laquelle les blocs libres sont adjacents sans être fusionnés. Vous pouvez utiliser une exception à partir de new, interceptée dans un new_handler, pour exécuter une procédure de compactage pour l'espace libre avant de réessayer.

Les programmes sérieux doivent traiter la mémoire comme une ressource potentiellement rare, contrôler autant que possible son allocation, surveiller sa disponibilité et réagir de manière appropriée si quelque chose semble s'être dramatiquement mal passé. Par exemple, vous pouvez faire valoir que, dans tout programme réel, la limite supérieure du paramètre size est transmise à l'allocateur de mémoire. Toute valeur supérieure à cette valeur doit entraîner une sorte de gestion des erreurs, que la requête soit ou non satisfait. Vous pouvez faire valoir que le taux d'augmentation de la mémoire d'un programme de longue durée doit être surveillé et que, si l'on peut raisonnablement prédire que le programme épuisera la mémoire disponible dans un proche avenir, un redémarrage ordonné du processus devrait être lancé.

16
Permaquid

Sur les systèmes Unix, il est habituel d'exécuter des processus longs avec des limites de mémoire (à l'aide de ulimit) afin de ne pas utiliser toute la mémoire d'un système. Si votre programme atteint cette limite, vous obtiendrez std::bad_alloc.


Mise à jour pour l'édition d'OP: le cas le plus typique de programmes qui récupèrent une condition de mémoire insuffisante concerne des systèmes ramassés à la poubelle, qui effectuent ensuite un GC et se poursuivent. Cependant, ce type de GC à la demande est uniquement destiné aux efforts de dernière minute; en général, les bons programmes essaient de mesurer périodiquement la CPG afin de réduire le stress du collecteur.

Il est moins courant pour les programmes non-GC de récupérer des problèmes de mémoire insuffisante, mais pour les serveurs connectés à Internet, une solution consiste à rejeter simplement la demande qui provoque l’épuisement de la mémoire avec une erreur "temporaire". (Stratégie "Premier entré, premier servi".)

9
Chris Jester-Young

osgx a dit:

Est-ce que toutes les applications du monde réel vérifie un grand nombre de nouvelles et peut récupérer quand il n'y a pas de mémoire?

J'ai déjà répondu à cela dans ma réponse à cette question , qui est citée ci-dessous:

Il est très difficile de gérer cela sorte de situation. Vous voudrez peut-être renvoyer une erreur significative à l'utilisateur de votre application, mais si c'est un problème causé par le manque de mémoire, vous peut même ne pas pouvoir se permettre le mémoire pour allouer le message d'erreur . C'est un peu un catch-22 situation vraiment.

Il y a une programmation défensive technique (parfois appelée mémoire parachute ou fonds de jours pluvieux) où vous allouez une partie de la mémoire lorsque votre l'application démarre. Quand vous alors gérer l’exception bad_alloc, vous libérez cette mémoire et utilisez le fichier mémoire disponible pour fermer le fichier demande gracieusement, y compris afficher une erreur significative pour le utilisateur. C'est beaucoup mieux que s'écraser :)

7

Cela dépend du compilateur/du moteur d’exécution et du operator new que vous utilisez (par exemple, certaines versions de Visual Studio ne jetteront pas les choses au hasard , mais renverront plutôt un pointeur NULL à la malloc.)

Vous pouvez toujours catch une exception std::bad_alloc ou utiliser explicitement nothrow new pour renvoyer NULL au lieu de lancer. (Voir aussi les articles précédents de StackOverflow tournant autour du sujet.)

Notez que operator new, comme malloc, will échouera lorsque vous manquerez de mémoire, d'espace d'adressage (par exemple, 2 à 3 Go dans un processus 32 bits selon le système d'exploitation), de quota (ulimit a déjà été mentionné ) ou en dehors de l’espace adresse contigu (par exemple, segment fragmenté.)

7
vladr

Vous n'avez pas besoin de gérer l'exception dans chaque new :) Les exceptions peuvent se propager. Concevez votre code de sorte qu'il y ait certains points dans chaque "module" où cette erreur est traitée.

7
moogs

Oui, new peut jeter std::bad_alloc (une sous-classe de std::exception), que vous pouvez attraper.

Si vous voulez absolument éviter cette exception et que vous êtes prêt à tester le résultat de new pour un pointeur NULL, vous pouvez ajouter un argument nothrow:

T* p = new (nothrow) T(...);
if (p == 0)
{
    // Do something about the bad allocation!
}
else
{
    // Here you may use p.
}
4
squelart

Oui new lèvera une exception s'il n'y a plus de mémoire disponible, mais cela ne signifie pas que vous devriez envelopper chaque nouveauté dans un try ... catch. Ne capturez l'exception que si votre programme peut réellement y remédier.

Si le programme ne peut rien faire pour gérer cette situation exceptionnelle, ce qui est souvent le cas si vous manquez de mémoire, il ne sert à rien d'attraper l'exception. Si la seule chose que vous pouvez raisonnablement faire est d’abandonner le programme, vous pouvez également laisser l’exception faire l’objet d’une bulle jusqu’au niveau supérieur, où elle mettra également fin au programme.

4
sth

Notez que sous Windows, les très grands nouveaux/mallocs allouent simplement à partir de la mémoire virtuelle. En pratique, votre ordinateur se bloquera avant que vous ne voyiez cette exception.

char *pCrashMyMachine = new char[TWO_GIGABYTES];

Essayez si vous osez!

3
Erik Hermansen

Dans de nombreux cas, il n’ya pas de récupération raisonnable pour une situation de mémoire insuffisante, auquel cas il est probablement parfaitement raisonnable de laisser l’application se terminer. Vous voudrez peut-être intercepter l’exception à un niveau élevé pour afficher un message d’erreur plus sympathique que celui fourni par le compilateur par défaut, mais vous devrez peut-être exécuter certaines astuces pour que cela fonctionne (étant donné que le processus risque d’être très faible). ressources à ce point).

À moins que vous n'ayez une situation particulière pouvant être gérée et récupérée, il n'y a probablement aucune raison de dépenser beaucoup d'efforts pour essayer de gérer l'exception.

3
Michael Burr

J'utilise Mac OS X et je n'ai jamais vu malloc return NULL (ce qui impliquerait une exception de new en C++). La machine ralentit, fait de son mieux pour allouer de la mémoire de plus en plus longue aux processus, puis envoie SIGSTOP et invite l'utilisateur à supprimer les processus au lieu de les laisser gérer les erreurs d'allocation.

Cependant, ce n'est qu'une plateforme. CERTAINEMENT il existe des plates-formes où l’allocateur par défaut jette. Et, comme le dit Chris, ulimit peut introduire une contrainte artificielle de sorte qu'une exception serait le comportement attendu.

En outre, il existe des allocateurs en plus de celui par défaut/malloc. Si une classe substitue operator new, vous utilisez des arguments personnalisés pour new(…) ou transmettez un objet allocateur à un conteneur, elle définit probablement ses propres conditions pour générer bad_alloc.

2
Potatoswatter

La fonction new-handler est la fonction appelée par les fonctions d'allocation chaque fois qu'une nouvelle tentative d'allocation de mémoire échoue. Nous pouvons avoir notre propre journalisation ou une action spéciale, par exemple, en organisant davantage de mémoire, etc. Son objectif est l’une des trois choses suivantes: 1) laisser plus de mémoire disponible 2) terminer le programme ( par exemple en appelant std :: terminate) 3) levent une exception de type std :: bad_alloc ou dérivée de std :: bad_alloc . L'implémentation par défaut lève std :: bad_alloc. L'utilisateur peut avoir son propre nouveau gestionnaire, qui peut offrir un comportement différent de celui par défaut. Cela ne devrait être utilisé que lorsque vous en avez vraiment besoin. Voir l'exemple pour plus de précisions et le comportement par défaut,

#include <iostream>
#include <new>

void handler()
{
    std::cout << "Memory allocation failed, terminating\n";
    std::set_new_handler(nullptr);
}

int main()
{
    std::set_new_handler(handler);
    try {
        while (true) {
            new int[100000000ul];
        }
    } catch (const std::bad_alloc& e) {
        std::cout << e.what() << '\n';
    }
}
1
Bhupesh Pant

Oui, nouvelle peut et va jeter.

Depuis que vous parlez de «vrais» programmes: je travaille sur diverses applications logicielles commerciales rétractables depuis plus de 20 ans. De «vrais» programmes avec des millions d'utilisateurs. Que vous pouvez aller acheter sur le marché aujourd'hui. Oui, nouveau peut lancer.

Il y a différentes façons de gérer cela.

Commencez par écrire votre propre new_handler (ceci est appelé avant que new abandonne et lève - voir la fonction set_new_handler ()). Lorsque votre new_handler est appelé, voyez si vous pouvez libérer des choses dont vous n’avez pas vraiment besoin. Avertissez également l'utilisateur qu'il manque de mémoire. (oui, il peut être difficile d'avertir l'utilisateur de quoi que ce soit si vous êtes vraiment faible).

Une chose est d’avoir pré-alloué, au start de votre programme, de la mémoire 'supplémentaire'. Lorsque vous manquez de mémoire, utilisez cette mémoire supplémentaire pour enregistrer une copie du document de l'utilisateur sur le disque. Ensuite, avertissez-vous et quittez peut-être avec élégance.

Etc. Ceci est juste un aperçu, il y a évidemment plus que cela.

Manipuler peu de mémoire n'est pas facile.

1
tony

Le plus réaliste des cas sera le nouveau en raison de la décision de limiter une ressource. Supposons que cette classe (qui consomme beaucoup de mémoire) prélève de la mémoire dans le pool physique et que si de nombreux objets y prennent (nous avons besoin de mémoire pour d’autres choses comme le son, les textures, etc.), elle risque de se jeter au lieu de tomber en panne être capable d'allouer de la mémoire le prend. (ressemble à un effet secondaire étrange).

La surcharge de données peut être utile dans les appareils à mémoire limitée. Tels que des ordinateurs de poche ou des consoles quand il est trop facile d'aller à la mer avec des effets sympas.

1
user34537

Il est bon de vérifier/intercepter cette exception lorsque vous allouez de la mémoire à partir de quelque chose provenant de l'extérieur (de l'utilisateur, du réseau, par exemple), car cela pourrait signifier une tentative de compromettre votre application/service/système se produire.

1
zoska

le nouvel opérateur lève une exception std :: bad_alloc lorsqu'il n'y a pas assez de mémoire disponible dans le pool pour répondre à la demande d'exécution.

Cela peut arriver avec une mauvaise conception ou lorsque la mémoire allouée n'est pas libérée correctement.

Le traitement de cette exception dépend de votre conception. Une des méthodes consiste à suspendre et à réessayer ultérieurement, en espérant que plus de mémoire sera restituée au pool et que la demande aboutira.

1
YeenFei

L'opérateur new lève une exception std::bad_alloc lorsque vous manquez de mémoire (mémoire virtuelle pour être précis). 

Si new lève une exception, c'est une grave erreur:

  • Plus que disponible VM est alloué (il finit par échouer). Vous pouvez essayer de réduire la quantité de mémoire par rapport à la sortie du programme en interceptant l'exception std::bad_alloc
0
aJ.