Anonim

AIMBOT 2.0

Nell'episodio 1 di New Game 2, intorno alle 9:40, c'è una ripresa del codice che Nene ha scritto:

Eccolo sotto forma di testo con i commenti tradotti:

// the calculation of damage when attacked void DestructibleActor::ReceiveDamage(float sourceDamage) { // apply debuffs auto resolvedDamage = sourceDamage; for (const auto& debuf:m_debufs) { resolvedDamage = debuf.ApplyToDamage(resolvedDamage); m_currentHealth -= resolvedDamage if (m_currentHealth <= 0.f) { m_currentHealth = 0.f; DestroyMe(); } } } 

Dopo lo scatto, Umiko, indicando il ciclo for, ha detto che il motivo per cui il codice si è bloccato è che c'è un ciclo infinito.

Non conosco veramente il C ++, quindi non sono sicuro che quello che sta dicendo sia vero.

Da quello che posso vedere, il ciclo for sta solo iterando attraverso i debuf che l'attore ha attualmente. A meno che l'attore non abbia una quantità infinita di debuf, non penso che possa diventare un loop infinito.

Ma non ne sono sicuro perché l'unico motivo per cui c'è una ripresa del codice è che volevano mettere un uovo di Pasqua qui, giusto? Avremmo appena ripreso il retro del laptop e sentito Umiko dire "Oh, hai un loop infinito lì". Il fatto che abbiano effettivamente mostrato del codice mi fa pensare che in qualche modo il codice sia un uovo di Pasqua di qualche tipo.

Il codice creerà effettivamente un ciclo infinito?

8
  • Probabilmente utile: screenshot aggiuntivo di Umiko che dice "Lo era chiamando la stessa operazione più e più volte ", che potrebbe non essere mostrato nel codice.
  • Oh! Non lo sapevo! @AkiTanaka il sottotitolo che ho visto dice "loop infinito"
  • @ LoganM Non sono davvero d'accordo. Non è solo che OP ha una domanda su un codice sorgente proveniente da un anime; La domanda di OP riguarda una particolare dichiarazione fatta di il codice sorgente di un personaggio nell'anime, e c'è una risposta relativa all'anime, vale a dire "Crunchyroll ha fatto uno scherzo e ha tradotto male la linea".
  • @senshin Penso che tu stia leggendo su cosa vuoi che sia la domanda, piuttosto che cosa viene effettivamente chiesto. La domanda fornisce del codice sorgente e chiede se genera un ciclo infinito come codice C ++ reale. Nuovo gioco! è un'opera di fantasia; non è necessario che il codice presentato in esso sia conforme agli standard della vita reale. Ciò che Umiko dice sul codice è più autorevole di qualsiasi standard o compilatore C ++. La risposta in alto (accettata) non menziona alcuna informazione nell'universo. Penso che una domanda sull'argomento potrebbe essere posta su questo con una buona risposta, ma come formulato non è così.

Il codice non è un ciclo infinito ma è un bug.

Ci sono due (forse tre) problemi:

  • Se non sono presenti debuf, non verrà applicato alcun danno
  • Se è presente più di 1 debuf, verrà applicato un danno eccessivo
  • Se DestroyMe () elimina immediatamente l'oggetto e ci sono ancora m_debuf da elaborare, il ciclo verrà eseguito su un oggetto eliminato e cestinerà la memoria. La maggior parte dei motori di gioco ha una coda di eliminazione per aggirare questo e altro, quindi potrebbe non essere un problema.

L'applicazione del danno dovrebbe essere al di fuori del ciclo.

Ecco la funzione corretta:

// the calculation of damage when attacked void DestructibleActor::ReceiveDamage(float sourceDamage) { // apply debuffs auto resolvedDamage = sourceDamage; for (const auto& debuf:m_debufs) { resolvedDamage = debuf.ApplyToDamage(resolvedDamage); } m_currentHealth -= resolvedDamage if (m_currentHealth <= 0.f) { m_currentHealth = 0.f; DestroyMe(); } } 
12
  • 15 Siamo in revisione del codice? : D
  • 4 galleggianti sono ottimi per la salute se non si supera 16777216 HP. Puoi persino impostare la salute su infinito per creare un nemico che puoi colpire ma non morire, e avere un attacco con una sola uccisione usando un danno infinito che comunque non ucciderà un personaggio HP infinito (il risultato di INF-INF è NaN) ucciderà tutto il resto. Quindi è molto utile.
  • 1 @cat Per convenzione in molti standard di codifica il file m_ prefisso significa che è una variabile membro. In questo caso una variabile membro di DestructibleActor.
  • 2 @HotelCalifornia Sono d'accordo che c'è una piccola possibilità ApplyToDamage non funziona come previsto ma nel caso esemplificativo che dai direi ApplyToDamage anche necessita di essere rielaborato per richiedere il passaggio dell'originale sourceDamage così da poter calcolare correttamente il debuf in quei casi. Per essere un pedante assoluto: a questo punto le informazioni su dmg dovrebbero essere una struttura che includa il dmg originale, il dmg corrente e la natura dei danni anche se i debuf hanno cose come "vulnerability to fire". Per esperienza non passa molto tempo prima che qualsiasi progetto di gioco con debuf lo richieda.
  • 1 @StephaneHockenhull ben detto!

Il codice non sembra creare un ciclo infinito.

L'unico modo in cui il ciclo sarebbe infinito sarebbe if

debuf.ApplyToDamage(resolvedDamage); 

o

DestroyMe(); 

dovessero aggiungere nuovi elementi al file m_debufs contenitore.

Sembra improbabile. E se fosse il caso, il programma potrebbe bloccarsi a causa della modifica del contenitore durante l'iterazione.

Il programma molto probabilmente andrebbe in crash a causa della chiamata a DestroyMe(); che presumibilmente distrugge l'oggetto corrente che sta attualmente eseguendo il ciclo.

Possiamo pensarlo come il cartone animato in cui il "cattivo" vede un ramo per far cadere il "bravo ragazzo", ma si rende conto troppo tardi di essere dalla parte sbagliata del taglio. O il serpente Midgaard che si mangia la coda.


Dovrei anche aggiungere che il sintomo più comune di un ciclo infinito è che blocca il programma o lo rende non reattivo. Si bloccherà il programma se alloca la memoria ripetutamente o fa qualcosa che finisce per dividere per zero o simili.


Sulla base del commento di Aki Tanaka,

Probabilmente utile: screenshot aggiuntivo di Umiko che diceva "Stava chiamando la stessa operazione più e più volte", che potrebbe non essere mostrato nel codice.

"Richiamava la stessa operazione più e più volte" Questo è più probabile.

Supponendo che DestroyMe(); non è progettato per essere chiamato più di una volta, è più probabile che provochi un arresto anomalo.

Un modo per risolvere questo problema sarebbe cambiare il file if per qualcosa di simile:

 if (m_currentHealth <= 0.f) { m_currentHealth = 0.f; DestroyMe(); break; } 

Questo uscirebbe dal ciclo quando il DestructibleActor viene distrutto, assicurandosi che 1) il file DestroyMe metodo viene chiamato solo una volta e 2) non applicare i buff inutilmente una volta che l'oggetto è già considerato morto.

2
  • 1 Uscire dal ciclo for quando health <= 0 è sicuramente una soluzione migliore che aspettare fino a dopo il ciclo per verificare lo stato di salute.
  • Penso che probabilmente lo farei break fuori dal giro, e poi chiamata DestroyMe(), solo per essere al sicuro

Ci sono diversi problemi con il codice:

  1. Se non ci sono debuf, non si subirà alcun danno.
  2. DestroyMe() il nome della funzione sembra pericoloso. A seconda di come è implementato, potrebbe o non potrebbe essere un problema. Se è solo una chiamata al distruttore dell'oggetto corrente racchiuso in una funzione, allora c'è un problema, poiché l'oggetto verrebbe distrutto durante l'esecuzione del codice. Se si tratta di una chiamata a una funzione che mette in coda l'evento di eliminazione dell'oggetto corrente, non ci sono problemi, poiché l'oggetto verrebbe distrutto dopo aver completato la sua esecuzione e il ciclo di eventi ha inizio.
  3. Il problema effettivo che sembra essere menzionato nell'anime, il "Richiamava la stessa operazione più e più volte" - chiamerà DestroyMe() fintanto che m_currentHealth <= 0.f e ci sono più debuff rimasti da iterare, il che potrebbe risultare in DestroyMe() essere chiamato più volte, ancora e ancora. Il ciclo dovrebbe interrompersi dopo il primo DestroyMe() chiamata, perché l'eliminazione di un oggetto più di una volta provoca il danneggiamento della memoria, che a lungo andare potrebbe causare un arresto anomalo.

Non sono proprio sicuro del motivo per cui ogni debuf porta via la salute, invece di toglierla solo una volta, con gli effetti di tutti i debuff applicati sul danno iniziale subito, ma presumo che sia la logica di gioco corretta.

Il codice corretto sarebbe

// the calculation of damage when attacked void DestructibleActor::ReceiveDamage(float sourceDamage) { // apply debuffs auto resolvedDamage = sourceDamage; for (const auto& debuf:m_debufs) { resolvedDamage = debuf.ApplyToDamage(resolvedDamage); m_currentHealth -= resolvedDamage if (m_currentHealth <= 0.f) { m_currentHealth = 0.f; DestroyMe(); break; } } } 
3
  • Devo sottolineare che poiché ho scritto allocatori di memoria in passato, l'eliminazione della stessa memoria non deve essere un problema. Potrebbe anche essere ridondante. Tutto dipende dal comportamento dell'allocatore. Il mio ha semplicemente agito come un elenco collegato di basso livello, quindi il "nodo" per i dati eliminati viene impostato come libero più volte o eliminato nuovamente più volte (il che corrisponderebbe solo a reindirizzamenti del puntatore ridondanti). Buona cattura però.
  • Double-free è un bug e generalmente porta a comportamenti non definiti e arresti anomali. Anche se hai un allocatore personalizzato che in qualche modo non consente il riutilizzo dello stesso indirizzo di memoria, double-free è un codice puzzolente in quanto non ha senso e verrai sgridato dagli analizzatori di codice statici.
  • Ovviamente! Non l'ho progettato per quello scopo. Alcune lingue richiedono solo un allocatore a causa della mancanza di funzionalità. No no no. Stavo semplicemente affermando che un incidente non è garantito. Alcune classificazioni di design non sempre si bloccano.