¿Cómo manejan los juegos de C ++ la falla de asignación de memoria?

23

Conozco varios juegos que están escritos en C ++ pero no usan excepciones. Dado que el manejo de la falla de asignación de memoria en C ++ generalmente se basa en la std::bad_allocexcepción, ¿cómo manejan estos juegos tal falla?

¿Simplemente se bloquean o hay otra forma de manejar y recuperarse de un error de falta de memoria?

usuario200783
fuente
1
Tenga en cuenta que hay ciertos entornos / SO en los que la asignación dice ser exitosa, pero al intentar usar la memoria se bloquea su (u otro) programa
PlasmaHH
66
Si la asignación falla durante un juego, Quake 3 lo regresará al menú con un mensaje de error. Creo que esto es posible gracias al asignador de grupos de Quake 3, que puede soltar todo el grupo y volver al menú de forma segura si el grupo se agota.
Dietrich Epp
16
Por lo general, al estrellarse.
user253751
1
Si las excepciones están realmente deshabilitadas, el programa debe llamar std::terminate, y eso es todo. Pero luego, bajo la misma restricción, la asignación puede devolver un puntero nulo en lugar de lanzar una excepción, y ese resultado se puede verificar y manejar por separado.
underscore_d
2
En C ++, puede decir ClassName variableName = new(nothrow) ClassName();( obviamente, reemplazar el nombre de la clase, el nombre de la variable, etc. ) Luego, si la asignación falla, puede detectarlo diciendo que if(!variableName)permite que el error se maneje sin un bloque de excepción try-catch. Del mismo modo, si la memoria se asigna usando una función como malloc(), calloc()etc., entonces las fallas de asignación se pueden detectar usando el mismo if(!variableName)método sin necesidad de un try-catch. En cuanto a cómo los juegos manejan estos errores, bueno, en ese punto, depende de los desarrolladores del juego decidir si se bloquea o no.
Spencer D

Respuestas:

63

Igual que todos los programas promedio: no lo hacen. *

Para la mayoría de las aplicaciones, los clientes no esperan que continúen funcionando una vez que se agote la memoria. Todos los juegos se incluyen en estas "la mayoría de las aplicaciones". Gastar tiempo y dinero para trabajar en un caso límite que el cliente no espera que funcione no tiene sentido.

La pregunta es similar a la siguiente:

  • ¿Cómo instalas un juego si el disco duro está lleno?
  • ¿Cómo ejecutas el juego a altas fps en una PC por debajo de las especificaciones mínimas?

La respuesta es la misma: estos son los problemas de los usuarios. Al juego no le importa.


* En realidad, generalmente hacen una captura de alto nivel de la excepción, y usan memoria que fue preasignada al comienzo del juego para intentar registrar el evento antes de estrellarse / terminar. El registro luego permite que el servicio al cliente pierda menos tiempo en el tema.

Peter - Unban Robert Harvey
fuente
2
Sobre la asignación
previa de
23

Por lo general, este tipo de escenario nunca sucede.

En primer lugar, la memoria virtual en los sistemas operativos modernos significa que es muy poco probable que suceda en el funcionamiento normal de todos modos; a menos que tenga un error de asignación fuera de control, el juego asignará memoria desde el espacio de direcciones virtuales del sistema operativo y el sistema operativo se ocupará de la entrada y la salida.

Eso está muy bien, pero en realidad no se aplica a consolas o sistemas operativos más antiguos, por lo que, en segundo lugar, los juegos ni siquiera hacen muchas asignaciones dinámicas pequeñas utilizando los asignadores de biblioteca estándar de todos modos. En cambio, lo que harán es asignar un gran conjunto de memoria una sola vez al inicio, y extraer de ese conjunto las asignaciones de tiempo de ejecución que sean necesarias (por ejemplo, mediante la colocación de C ++ nueva o escribiendo su propio sistema de administración de memoria en arriba de eso).

Eso también protege contra pérdidas de memoria porque, en lugar de tener que rastrear y dar cuenta de cada pequeña asignación individual, un juego puede tirar todo el grupo para recuperar memoria.

Y en tercer lugar, los juegos siempre establecerán una especificación mínima y presupuestarán su uso de memoria dentro de eso. Entonces, si se especifica que un juego requiere 1 gb de RAM, eso significa que mientras su uso de memoria nunca exceda 1 gb, nunca tendrá que preocuparse por quedarse sin RAM.

Maximus Minimus
fuente
3
"En cambio, lo que harán es asignar un gran grupo de memoria una sola vez al inicio, y extraer de ese grupo cualquier asignación de tiempo de ejecución que sea necesaria", ¿y qué crees que sucede si el grupo está lleno?
user253751
@immibis - mira mi tercer punto por favor.
Maximus Minimus
2
@immibis Quieres decir ... ¿qué hacen si la piscina está vacía? A diferencia de la memoria del sistema, el tamaño y el uso del grupo están completamente bajo el control de la aplicación. Por lo tanto, el grupo no puede quedar vacío a menos que la aplicación tenga un error.
David Schwartz
@DavidSchwartz O a menos que el kernel esté usando un exceso de memoria. Linux hace esto. Incluso poner a cero su grupo no ayuda si la compresión de archivos de página está habilitada a través de zswap o zram.
Damian Yerrick
1
Esto no parece responder a la pregunta real, sino que establece algo similar a "Esta nave es insumergible, ¿para qué necesitaríamos un procedimiento de emergencia?"
Lilienthal
2

Bueno, principalmente, de la misma manera que lo hicimos antes de que existieran excepciones: el antiguo "verificar el enfoque del valor de retorno". Si un asignador no utiliza excepciones, generalmente regresará nullcuando falle una asignación. Un juego bien escrito verificará eso y manejará la situación de cualquier manera que sea segura. A veces, esto significa terminar el juego (p. Ej.std::terminate . ) O al menos volver al último estado seguro conocido (p. Ej., Cuando asigna desde un grupo, puede deshacerse de todo el grupo de forma segura incluso cuando esté en un estado inseguro).

Muchos juegos usan excepciones para casos como este, incluso entonces. Cuando alguien dice "evita usar excepciones en el código del juego", lo que generalmente significan es "solo usa excepciones para casos verdaderamente excepcionales, no para el control de flujo mundano". La configuración para capturar una excepción de falta de memoria es barata: el costo está principalmente en el lanzamiento y las complicaciones que se obtienen al manejar la excepción. Ninguno de los dos es muy importante en un caso típico de falta de memoria: casi siempre desea terminar de todos modos.

Eso sí, la mayoría de los juegos simplemente se bloquean. Esto no es tan loco como parece: por lo general, tienes un poco de uso de memoria como objetivo, y asegúrate de que tu juego no alcance ese límite (por ejemplo, al usar un límite de conteo de unidades en un juego de estrategia en tiempo real o al limitar el cantidad de enemigos en una sección de un FPS). Incluso si no lo hace, generalmente no se queda sin memoria en ningún sistema razonablemente moderno; por ejemplo, se queda sin espacio de direcciones virtuales (en una aplicación de 32 bits) o la pila. El sistema operativo ya finge que tienes tanta memoria como pides, esa es una de las abstracciones que proporciona la memoria virtual. El punto es que solo porque tienes 256 MiB de RAM física no significa que tu aplicación no pueda usar 4 GiB de memoria. Es posible que a algunos juegos ni siquiera les importe demasiado que todos sus datos no

Luaan
fuente
1

Capturar una excepción y decidir qué hacer cuando se genera una excepción son dos cosas diferentes.

Debe tener una lógica compleja para liberar memoria que su programa no necesita. Peor aún, si cualquier otro programa o biblioteca en ejecución pierde la memoria, entonces no tiene control sobre ella.

Lo único bueno que puedes hacer es apagarlo con gracia y eso es lo que la mayoría de los programas elegirán hacer. Ni siquiera puede decidir esperar hasta que la memoria esté disponible porque hará que su programa no responda.

Daksh
fuente
Si los juegos en cuestión literalmente "no usan excepciones" como dijo el OP, entonces no pueden atrapar, aumentar o procesar uno. Si las excepciones están deshabilitadas y alguien lanza una, el programa debe llamar std::terminate, y eso es todo. Pero en tales condiciones, la asignación puede devolver un puntero nulo en lugar de lanzar una excepción, y eso se puede manejar por separado.
underscore_d