Como os jogos C ++ lidam com falha na alocação de memória?

23

Estou ciente de vários jogos que são escritos em C ++, mas não usam exceções. Como o tratamento da falha de alocação de memória no C ++ geralmente é criado em torno da std::bad_allocexceção, como esses jogos lidam com essa falha?

Eles simplesmente travam ou existe outra maneira de lidar e se recuperar de um erro de falta de memória?

user200783
fonte
1
Note que existem certos ambientes / OSs onde as reivindicações de alocação para ser bem sucedido, mas tentar usar a memória falhou em seu (ou outro) programa
PlasmaHH
6
Se a alocação falhar durante um jogo, o Quake 3 o levará de volta ao menu com uma mensagem de erro. Acredito que isso seja possível pelo alocador de pool do Quake 3, que pode simplesmente largar o pool inteiro e retornar ao menu com segurança se o pool acabar.
Dietrich Epp
16
Geralmente, batendo.
precisa saber é o seguinte
1
Se as exceções estiverem genuinamente desabilitadas, o programa deverá chamar std::terminate, e é isso. Mas, sob a mesma restrição, a alocação pode retornar um ponteiro nulo em vez de lançar uma exceção, e esse resultado pode ser verificado e tratado separadamente.
Underscore_d
2
No C ++, você pode dizer ClassName variableName = new(nothrow) ClassName();( obviamente, substituindo o nome da classe, o nome da variável etc. ). Se a alocação falhar, você poderá detectá-lo dizendo if(!variableName)permitindo que o erro seja tratado sem um bloco de exceção try-catch. Da mesma forma, se a memória é alocada usando uma função como malloc(), calloc()etc., as falhas na alocação podem ser detectadas usando o mesmo if(!variableName)método sem a necessidade de uma tentativa de captura. Quanto à forma como os jogos lidam com esses erros, bem, nesse ponto, cabe aos desenvolvedores decidir se ele trava ou não.
Spencer D

Respostas:

63

O mesmo que todos os programas comuns: eles não. *

Para a maioria dos aplicativos, não há expectativa de que eles continuem trabalhando quando a memória acabar. Todos os jogos se enquadram nessas "maioria dos aplicativos". Gastar tempo e dinheiro para trabalhar em um caso de ponta que o cliente não espera trabalhar não faz sentido.

A pergunta é semelhante à seguinte:

  • Como você instala um jogo se o disco rígido está cheio?
  • Como você ainda roda o jogo em alta velocidade em um PC abaixo das especificações mínimas?

A resposta é a mesma: estes são os problemas dos usuários. O jogo não se importa.


* Na verdade, eles geralmente capturam a exceção de alto nível e usam a memória pré-alocada no início do jogo para tentar registrar o evento antes de travar / terminar. O log permite que o suporte ao cliente perca menos tempo com o problema.

Peter - Unban Robert Harvey
fonte
2
Na pré-alocação de memória para log etc. no caso de falha na alocação de memória: stackoverflow.com/questions/7749066/…
#
23

Normalmente, esse tipo de cenário nunca acontece.

Em primeiro lugar, a memória virtual em sistemas operacionais modernos significa que é altamente improvável que ocorra na operação normal de qualquer maneira; a menos que você tenha um erro de alocação descontrolada, o jogo alocará memória do espaço de endereço virtual do SO e o SO cuidará da paginação dentro e fora.

Está tudo bem, mas na verdade não se aplica a consoles ou sistemas operacionais mais antigos; portanto, em segundo lugar, os jogos nem fazem muitas pequenas alocações dinâmicas usando alocadores de biblioteca padrão de qualquer maneira. Em vez disso, o que eles farão é alocar um grande conjunto de memória apenas uma vez na inicialização e retirar desse pool qualquer alocação de tempo de execução necessária (por exemplo, usando o posicionamento C ++ novo ou escrevendo seu próprio sistema de gerenciamento de memória em em cima).

Isso também protege contra vazamentos de memória porque, em vez de ter que rastrear e contabilizar cada alocação individual pequena, um jogo pode simplesmente jogar fora todo o pool para recuperar a memória.

E em terceiro lugar, os jogos sempre definirão uma especificação mínima e incluirão no orçamento seu uso de memória. Portanto, se um jogo for especificado para exigir 1 GB de RAM, isso significa que, enquanto o uso da memória nunca exceder 1 GB, ele nunca precisará se preocupar com a falta de RAM.

Maximus Minimus
fonte
3
"Em vez disso, o que eles farão é alocar um grande conjunto de memória apenas uma vez na inicialização e retirar desse pool qualquer alocação de tempo de execução necessária" - e o que você acha que acontece se o pool estiver cheio?
precisa saber é o seguinte
@immibis - veja meu terceiro ponto, por favor.
Maximus Minimus 08/08
2
@immibis Você quer dizer ... o que eles fazem se a piscina estiver vazia? Diferentemente da memória do sistema, o tamanho e o uso do pool estão completamente sob o controle do aplicativo. Portanto, o pool não pode ficar vazio a menos que o aplicativo tenha um erro.
David Schwartz
@DavidSchwartz Ou, a menos que o kernel esteja usando overcommit de memória. Linux faz isso. Mesmo zerar seu pool não ajuda se a compactação do arquivo de paginação estiver ativada via zswap ou zram.
Damian Yerrick
1
Isso não parece responder à pergunta real, mas afirma algo semelhante a "Este navio não pode ser afundado, então para que precisaríamos de um procedimento de emergência?"
Lilienthal
2

Bem, principalmente, da mesma maneira que fizemos antes das exceções - o antigo "verifique a abordagem do valor de retorno". Se um alocador não usar exceções, geralmente retornará nullquando uma alocação falhar. Um jogo bem escrito verificará isso e lidará com a situação da maneira que for mais segura. Às vezes, isso significa encerrar o jogo (por exemplo std::terminate) ou, pelo menos, reverter para o último estado seguro conhecido (por exemplo, quando você aloca a partir de um pool, você pode descartar com segurança todo o pool mesmo quando este estiver em um estado inseguro).

Muitos jogos usam exceções para casos como esse mesmo então. Quando alguém diz "evite usar exceções no código do jogo", o que geralmente significa é "use somente exceções para casos verdadeiramente excepcionais, não para controle de fluxo mundano". A configuração para capturar uma exceção de falta de memória é barata - o custo é maioritariamente e as complicações que você obtém ao lidar com a exceção. Nenhum deles é muito importante em um caso típico de falta de memória - você quase sempre deseja terminar de qualquer maneira.

Lembre-se, a maioria dos jogos irá falhar. Isso não é tão louco quanto parece - você normalmente usa um pouco de memória como alvo e garante que seu jogo não atinja esse limite (por exemplo, usando um limite de contagem de unidades em um jogo RTS ou limitando o quantidade de inimigos em uma seção de um FPS). Mesmo se não o fizer, normalmente não fica sem memória em nenhum sistema razoavelmente moderno - fica sem espaço de endereço virtual (em um aplicativo de 32 bits) ou na pilha, por exemplo. O sistema operacional já finge que você tem tanta memória quanto solicita - essa é uma das abstrações que a memória virtual fornece. O ponto é que, apenas porque você tem 256 MiB de RAM física, isso não significa que seu aplicativo não pode usar 4 GiB de memória. Alguns jogos podem nem se importar muito com o fato de todos os dados não estarem '

Luaan
fonte
1

Capturar uma exceção e decidir fazer o que quando e exceção é gerada são duas coisas diferentes.

Você precisa ter uma lógica complexa para liberar memória que não é necessária pelo seu programa. Pior ainda, se algum outro programa ou biblioteca em execução estiver vazando na memória, você não terá controle sobre ele

A única coisa boa que você pode fazer é desligar o dispositivo normalmente e é isso que a maioria dos programas escolherá fazer. Você nem pode decidir esperar até que a memória esteja disponível, pois isso tornará seu programa sem resposta.

Daksh
fonte
Se os jogos em questão literalmente "não usam exceções", como o OP disse, eles não podem capturar, aumentar ou processar um. Se as exceções estão desabilitadas e alguém lança uma, o programa deve chamar std::terminate, e é isso. Mas nessas condições, a alocação pode retornar um ponteiro nulo em vez de lançar uma exceção, e isso pode ser tratado separadamente.
Underscore_d