Comment les jeux C ++ gèrent-ils les échecs d'allocation de mémoire?

23

Je connais plusieurs jeux qui sont écrits en C ++ mais n'utilisent pas d'exceptions. Étant donné que la gestion des échecs d'allocation de mémoire en C ++ est généralement construite autour de l' std::bad_allocexception, comment ces jeux gèrent-ils un tel échec?

Se bloquent-ils simplement ou existe-t-il un autre moyen de gérer et de récupérer après une erreur de mémoire insuffisante?

user200783
la source
1
Notez qu'il y a certains environnements / OS où l'allocation prétend être réussie, mais essayer d'utiliser la mémoire bloque votre (ou autre) programme
PlasmaHH
6
Si l'allocation échoue pendant un jeu, Quake 3 vous ramènera au menu avec un message d'erreur. Je crois que cela est rendu possible par l'allocateur de pool de Quake 3, qui peut simplement supprimer l'ensemble du pool et revenir au menu en toute sécurité si la piscine s'épuise.
Dietrich Epp
16
Habituellement, en plantant.
user253751
1
Si les exceptions sont réellement désactivées, le programme doit plutôt appeler std::terminate, et c'est tout. Mais sous la même restriction, l'allocation peut renvoyer un pointeur nul au lieu de lever une exception, et ce résultat peut être vérifié et géré séparément.
underscore_d
2
En C ++, vous pouvez dire ClassName variableName = new(nothrow) ClassName();( évidemment en remplaçant le nom de classe, le nom de variable, etc. ) Ensuite, si l'allocation échoue, vous pouvez la détecter en disant de if(!variableName)permettre à l'erreur d'être gérée sans bloc d'exception try-catch. De même, si la mémoire est allouée à l' aide d' une fonction comme malloc(), calloc(), etc., puis les échecs d'affecter peut être détectée en utilisant la même if(!variableName)méthode sans avoir besoin d' un try-catch. Quant à la façon dont les jeux gèrent ces erreurs, eh bien, à ce stade, il appartient aux développeurs du jeu de décider s'il se bloque ou non.
Spencer D

Réponses:

63

Identique à tous les programmes moyens: ils ne le font pas. *

Pour la plupart des applications, les clients ne s'attendent pas à ce qu'ils continuent de fonctionner une fois la mémoire épuisée. Tous les jeux relèvent de la "plupart des applications". Dépenser du temps et de l'argent pour travailler sur un boîtier de pointe que le client ne s'attend pas à travailler n'a pas de sens.

La question est similaire à la suivante:

  • Comment installer un jeu si le disque dur est plein?
  • Comment continuez-vous à exécuter le jeu à des images par seconde élevées sur un PC en dessous des spécifications minimales?

La réponse est la même: ce sont les problèmes des utilisateurs. Le jeu s'en fiche.


* En fait, ils font généralement une capture de haut niveau de l'exception et utilisent la mémoire qui a été pré-allouée au début du jeu afin d'essayer de consigner l'événement avant de planter / terminer. Le journal permet ensuite au support client de perdre moins de temps sur le problème.

Peter - Unban Robert Harvey
la source
2
Sur la préallocation de mémoire pour la journalisation, etc. en cas d'échec d'allocation de mémoire: stackoverflow.com/questions/7749066/…
bwDraco
23

En règle générale, ce genre de scénario ne se produit jamais.

Premièrement, la mémoire virtuelle sur les systèmes d'exploitation modernes signifie qu'il est peu probable qu'il se produise de toute façon en fonctionnement normal; sauf si vous avez un bogue d'allocation galopant, le jeu allouera de la mémoire à partir de l'espace d'adressage virtuel du système d'exploitation et le système d'exploitation s'occupera de la pagination à l'intérieur et à l'extérieur.

C'est bien beau, mais cela ne s'applique pas vraiment aux consoles ou aux anciens systèmes d'exploitation, donc deuxièmement, les jeux ne font même pas beaucoup de petites allocations dynamiques en utilisant des allocateurs de bibliothèque standard de toute façon. Au lieu de cela, ils vont allouer un grand pool de mémoire une seule fois au démarrage et tirer de ce pool pour toutes les allocations d'exécution qui sont nécessaires (par exemple en utilisant le placement C ++ nouveau ou en écrivant leur propre système de gestion de la mémoire sur dessus).

Cela protège également contre les fuites de mémoire, car plutôt que d'avoir à suivre et à rendre compte de chaque petite allocation individuelle, un jeu peut simplement jeter tout le pool pour récupérer de la mémoire.

Et troisièmement, les jeux définiront toujours une spécification minimale et budgétiseront leur utilisation de mémoire à l'intérieur de cela. Donc, si un jeu est spécifié pour nécessiter 1 Go de RAM, cela signifie que tant que son utilisation de la mémoire ne dépasse jamais 1 Go, il n'a jamais à se soucier de manquer de RAM.

Maximus Minimus
la source
3
"Au lieu de cela, ils vont allouer un grand pool de mémoire une seule fois au démarrage et tirer de ce pool pour toutes les allocations d'exécution requises" - et que pensez-vous qu'il se passe si le pool est plein?
user253751
@immibis - voir mon troisième point s'il vous plaît.
Maximus Minimus
2
@immibis Vous voulez dire ... que font-ils si la piscine est vide? Contrairement à la mémoire système, la taille et l'utilisation du pool sont entièrement sous le contrôle de l'application. Le pool ne peut donc pas devenir vide à moins que l'application n'ait un bug.
David Schwartz
@DavidSchwartz Ou à moins que le noyau n'utilise une surcharge de mémoire. Linux fait cela. Même la mise à zéro de votre pool n'aide pas si la compression du fichier d'échange est activée via zswap ou zram.
Damian Yerrick
1
Cela ne semble pas répondre à la question réelle, mais indique plutôt quelque chose de similaire à "Ce navire est insubmersible, alors pourquoi aurions-nous besoin d'une procédure d'urgence?"
Lilienthal
2

Eh bien, principalement, de la même manière que nous le faisions avant que des exceptions n'existent - l'ancienne «approche de la valeur de retour». Si un allocateur n'utilise pas d'exceptions, il revient généralement en nullcas d'échec d'une allocation. Un jeu bien écrit vérifiera cela et gérera la situation de quelque manière que ce soit en toute sécurité. Parfois, cela signifie mettre fin au jeu (par exemple std::terminate) ou au moins revenir au dernier état sûr connu (par exemple, lorsque vous allouez à partir d'un pool, vous pouvez éliminer en toute sécurité le pool entier même lorsqu'il est dans un état dangereux).

De nombreux jeux utilisent des exceptions pour des cas comme celui-là même alors. Quand quelqu'un dit "éviter d'utiliser des exceptions dans le code du jeu", ce qu'il veut généralement dire est "n'utiliser des exceptions que pour des cas vraiment exceptionnels, pas pour un contrôle de flux banal". La configuration pour intercepter une exception de mémoire insuffisante est bon marché - le coût est principalement dans le lancer et les complications que vous obtenez avec la gestion de l'exception. Aucun de ces éléments n'est très important dans un cas typique de mémoire insuffisante - vous voulez presque toujours terminer de toute façon.

Attention, la plupart des jeux plantent. Ce n'est pas aussi fou que cela puisse paraître - vous avez généralement une certaine utilisation de la mémoire comme cible, et assurez-vous que votre jeu n'atteint pas cette limite (par exemple, en utilisant une limite de nombre d'unités dans un jeu RTS, ou en limitant la quantité d'ennemis dans une section d'un FPS). Même si vous ne le faites pas, vous ne manquez généralement pas de mémoire sur un système raisonnablement moderne - vous manquez d'espace d'adressage virtuel (dans une application 32 bits) ou de la pile, par exemple. Le système d'exploitation prétend déjà que vous avez autant de mémoire que vous demandez - c'est l'une des abstractions que la mémoire virtuelle fournit. Le fait est que le fait que vous ayez 256 Mo de RAM physique ne signifie pas que votre application ne peut pas utiliser 4 Go de mémoire. Certains jeux peuvent même ne pas trop se soucier du fait que toutes leurs données ne sont pas

Luaan
la source
1

Attraper une exception et décider de faire quoi quand une exception est levée sont deux choses différentes.

Vous devez disposer d'une logique complexe pour libérer de la mémoire dont votre programme n'a pas besoin. Pire encore, si un autre programme ou bibliothèque en cours d'exécution fuit la mémoire, vous n'avez aucun contrôle dessus

Seule bonne chose, vous pouvez le faire pour arrêter avec élégance et c'est ce que la plupart des programmes choisissent de faire. Vous ne pouvez même pas décider d'attendre que la mémoire soit disponible car cela rendra votre programme insensible.

Daksh
la source
Si les jeux en question "n'utilisent pas d'exceptions" comme le dit l'OP, alors ils ne peuvent pas en attraper, augmenter ou traiter un. Si les exceptions sont désactivées et que quelqu'un en lance une, le programme doit plutôt appeler std::terminate, et c'est tout. Mais dans de telles conditions, l'allocation peut renvoyer un pointeur nul au lieu de lever une exception, et cela peut être géré séparément.
underscore_d