In che modo i giochi C ++ gestiscono l'errore di allocazione della memoria?


23

Sono a conoscenza di diversi giochi che sono scritti in C ++ ma non usano eccezioni. Poiché la gestione dell'errore di allocazione della memoria in C ++ si basa generalmente std::bad_allocsull'eccezione, in che modo questi giochi gestiscono tale errore?

Si arrestano semplicemente in modo anomalo o esiste un altro modo per gestire e ripristinare un errore di memoria insufficiente?


1
Nota che ci sono alcuni ambienti / sistemi operativi in ​​cui l'allocazione afferma di avere successo, ma il tentativo di utilizzare la memoria
provoca l'arresto anomalo del

6
Se l'allocazione fallisce durante una partita, Quake 3 ti riporterà al menu con un messaggio di errore. Credo che ciò sia reso possibile dall'allocatore di pool di Quake 3, che può semplicemente eliminare l'intero pool e tornare al menu in modo sicuro se il pool si esaurisce.
Dietrich Epp

16
Di solito, schiantandosi.
user253751

1
Se le eccezioni sono veramente disabilitate, il programma deve invece chiamare std::terminatee basta. Tuttavia, con la stessa restrizione, l'allocazione può restituire un puntatore null invece di generare un'eccezione e quel risultato può essere verificato e gestito separatamente.
underscore_d

2
In C ++ puoi dire ClassName variableName = new(nothrow) ClassName();( ovviamente sostituendo il nome della classe, il nome della variabile, ecc. ) Quindi, se l'allocazione fallisce, puoi rilevarla dicendo che if(!variableName)consente di gestire l'errore senza un blocco di eccezioni try-catch. Allo stesso modo, se la memoria viene allocata utilizzando una funzione come malloc(), calloc()ecc., Gli errori da allocare possono essere rilevati usando lo stesso if(!variableName)metodo senza bisogno di un try-catch. Per quanto riguarda il modo in cui i giochi gestiscono questi errori, beh, a quel punto, spetta agli sviluppatori del gioco decidere se si blocca o meno.
Spencer D

Risposte:


63

Come tutti i programmi medi: non lo fanno. *

Per la maggior parte delle applicazioni, i clienti non si aspettano che continuino a funzionare una volta esaurita la memoria. Tutti i giochi rientrano in questa "maggior parte delle applicazioni". Trascorrere tempo e denaro per lavorare su un caso limite che il cliente non si aspetta di lavorare non ha senso.

La domanda è simile alla seguente:

  • Come si installa un gioco se il disco rigido è pieno?
  • Come si esegue ancora il gioco ad alti fps su un PC al di sotto delle specifiche minime?

La risposta è la stessa: questi sono i problemi degli utenti. Al gioco non importa.


* In realtà, di solito eseguono un rilevamento di alto livello dell'eccezione e usano la memoria che è stata pre-allocata all'inizio del gioco al fine di provare a registrare l'evento prima di arrestarsi / terminare. Il registro consente quindi all'assistenza clienti di perdere meno tempo sulla questione.


2
Su preallocare memoria per la registrazione, ecc in caso di errore di allocazione della memoria: stackoverflow.com/questions/7749066/...
bwDraco

23

In genere questo tipo di scenario non si verifica mai.

In primo luogo, la memoria virtuale sui moderni sistemi operativi significa che è altamente improbabile che accada durante il normale funzionamento; a meno che tu non abbia un bug di allocazione in fuga, il gioco allocerà la memoria dallo spazio di indirizzi virtuale del sistema operativo e il sistema operativo si occuperà del paging in entrata e in uscita.

Va bene e va bene, ma in realtà non si applica a console o sistemi operativi precedenti, quindi in secondo luogo, i giochi non eseguono nemmeno molte piccole allocazioni dinamiche utilizzando gli allocatori di librerie standard. Invece, ciò che faranno è allocare un ampio pool di memoria una sola volta all'avvio e disegnare da quel pool per tutte le allocazioni di runtime richieste (ad esempio utilizzando il posizionamento C ++ nuovo o scrivendo il proprio sistema di gestione della memoria su sopra).

Ciò protegge anche dalle perdite di memoria perché, anziché dover tracciare e tenere conto di ogni singola allocazione individuale, un gioco può semplicemente buttare via l'intero pool per recuperare la memoria.

E in terzo luogo, i giochi imposteranno sempre una specifica minima e prevederanno il loro utilizzo della memoria. Quindi, se un gioco viene specificato per richiedere 1 GB di RAM, ciò significa che fino a quando il suo utilizzo della memoria non supera mai 1 GB, non deve mai preoccuparsi di rimanere senza RAM.


3
"Invece quello che faranno è allocare un grande pool di memoria una sola volta all'avvio e disegnare da quel pool per tutte le allocazioni di runtime richieste" - e cosa pensi che accada se il pool è pieno?
user253751

@immibis - vedi il mio terzo punto per favore.
Maximus Minimus

2
@immibis Intendi ... cosa fanno se la piscina è vuota? A differenza della memoria di sistema, le dimensioni e l'utilizzo del pool sono completamente sotto il controllo dell'applicazione. Quindi il pool non può diventare vuoto a meno che l'applicazione non abbia un bug.
David Schwartz,

@DavidSchwartz O a meno che il kernel non stia utilizzando un overcommit di memoria. Linux fa questo. Anche l'azzeramento del pool non aiuta se la compressione del file di paging è abilitata tramite zswap o zram.
Damian Yerrick,

1
Questo non sembra rispondere alla domanda reale ma afferma invece qualcosa di simile a "Questa nave è inaffondabile, quindi per cosa avremmo bisogno di una procedura di emergenza?"
Lilienthal,

2

Bene, principalmente, allo stesso modo in cui lo abbiamo fatto prima che esistessero eccezioni: il vecchio "controlla l'approccio del valore di ritorno". Se un allocatore non utilizza eccezioni, di solito restituisce nullquando un'allocazione non riesce. Un gioco ben scritto verificherà ciò e gestirà la situazione in qualunque modo sia sicuro. A volte, questo significa terminare il gioco (ad es. std::terminate) O tornare all'ultimo stato sicuro noto (ad es. Quando si assegna da un pool, è possibile disporre in sicurezza dell'intero pool anche quando si trova in uno stato non sicuro).

Molti giochi usano eccezioni anche per casi come questo. Quando qualcuno dice "evita di usare le eccezioni nel codice di gioco", ciò che di solito significa è "usa le eccezioni solo per casi veramente eccezionali, non per un banale controllo del flusso". L'impostazione per la rilevazione di un'eccezione di memoria esaurita è economica: il costo è principalmente a rischio e le complicazioni che si verificano con la gestione dell'eccezione. Nessuno di questi è molto importante in un tipico caso di memoria insufficiente: si desidera quasi sempre terminare comunque.

Intendiamoci, la maggior parte dei giochi andrà in crash. Questo non è così folle come sembra - di solito hai un po 'di utilizzo della memoria come bersaglio e assicurati che il tuo gioco non raggiunga quel limite (ad esempio, usando un limite di conteggio unità in un gioco RTS o limitando il quantità di nemici in una sezione di un FPS). Anche se non lo fai, di solito non esaurisci la memoria su nessun sistema ragionevolmente moderno, ad esempio esaurisci lo spazio degli indirizzi virtuali (in un'applicazione a 32 bit) o ​​lo stack. Il sistema operativo finge già di avere tutta la memoria richiesta, questa è una delle astrazioni fornite dalla memoria virtuale. Il punto è, solo perché hai 256 MiB di RAM fisica non significa che l'applicazione non può utilizzare 4 GiB di memoria. Alcuni giochi potrebbero non preoccuparsi troppo che tutti i loro dati non siano


1

Prendere un'eccezione e decidere di fare cosa quando e quando viene sollevata l'eccezione sono due cose diverse.

È necessario disporre di una logica complessa per liberare memoria che non è necessaria per il programma. Peggio ancora, se qualsiasi altro programma o libreria in esecuzione perde la memoria, non hai alcun controllo su di essa

L'unica cosa positiva è che puoi farlo per spegnerlo con grazia ed è quello che la maggior parte dei programmi sceglierà di fare. Non puoi nemmeno decidere di aspettare fino a quando la memoria sarà disponibile perché renderà il tuo programma non rispondente.


Se i giochi in questione letteralmente "non usano eccezioni" come ha detto l'OP, allora non possono catturarli, rilanciarli o elaborarli. Se le eccezioni sono disabilitate e qualcuno ne lancia una, il programma deve invece chiamare std::terminatee basta. Tuttavia, in tali condizioni, l'allocazione può restituire un puntatore nullo anziché generare un'eccezione e può essere gestita separatamente.
underscore_d
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.