Anonim

AIMBOT 2.0

En el episodio 1 de New Game 2, alrededor de las 9:40, hay una toma del código que ha escrito Nene:

Aquí está en forma de texto con los comentarios traducidos:

// 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(); } } } 

Después del disparo, Umiko, señalando el bucle for, dijo que la razón por la que el código fallaba es que hay un bucle infinito.

Realmente no sé C ++, así que no estoy seguro de si lo que dice es cierto.

Por lo que puedo ver, el bucle for solo está iterando a través de las debufs que tiene el actor actualmente. A menos que el actor tenga una cantidad infinita de debufs, no creo que pueda convertirse en un bucle infinito.

Pero no estoy seguro porque la única razón por la que hay una toma del código es que querían poner un huevo de Pascua aquí, ¿verdad? Habríamos obtenido una toma de la parte posterior de la computadora portátil y escuchado a Umiko decir "Oh, tienes un bucle infinito allí". El hecho de que realmente mostraran algún código me hace pensar que de alguna manera el código es un huevo de Pascua de algún tipo.

¿El código realmente creará un bucle infinito?

8
  • Probablemente útil: captura de pantalla adicional de Umiko que dice "Fue llamando a la misma operación una y otra vez ", que puede que no se muestre en el código.
  • ¡Oh! ¡No lo sabía! @AkiTanaka el sub que vi dice "bucle infinito"
  • @LoganM Realmente no estoy de acuerdo. No es solo que OP tiene una pregunta sobre algún código fuente que resultó de un anime; La pregunta de OP es sobre una declaración particular hecha sobre el código fuente de un personaje en el anime, y hay una respuesta relacionada con el anime, a saber, "Crunchyroll hizo una tontería y tradujo mal la línea".
  • @senshin Creo que estás leyendo sobre lo que quieres que se trate la pregunta, en lugar de lo que realmente se pregunta. La pregunta proporciona algo de código fuente y pregunta si genera un bucle infinito como código C ++ de la vida real. ¡Nuevo juego! es una obra de ficción; no es necesario que el código presentado en él se ajuste a los estándares de la vida real. Lo que dice Umiko sobre el código tiene más autoridad que cualquier estándar o compilador de C ++. La respuesta superior (aceptada) no menciona ninguna información en el universo. Creo que se podría hacer una pregunta sobre el tema con una buena respuesta, pero tal como está expresado, no es así.

El código no es un bucle infinito pero es un error.

Hay dos (posiblemente tres) problemas:

  • Si no hay debufs, no se aplicará ningún daño.
  • Se aplicará un daño excesivo si hay más de 1 debuf
  • Si DestroyMe () elimina inmediatamente el objeto y todavía quedan m_debufs por procesar, el bucle se ejecutará sobre un objeto eliminado y destruirá la memoria. La mayoría de los motores de juegos tienen una cola de destrucción para solucionar esto y más, por lo que puede que no sea un problema.

La aplicación del daño debe estar fuera del circuito.

Aquí está la función corregida:

// 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 ¿Estamos en revisión de código? :D
  • 4 flotadores son excelentes para la salud si no supera los 16777216 HP. Incluso puedes establecer la salud en infinito para crear un enemigo al que puedas golpear pero que no muera, y tener un ataque de una muerte con daño infinito que aún no matará a un personaje de HP infinito (el resultado de INF-INF es NaN) pero matará todo lo demás. Entonces es muy útil.
  • 1 @cat Por convención en muchos estándares de codificación, m_ prefijo significa que es una variable miembro. En este caso, una variable miembro de DestructibleActor.
  • 2 @HotelCalifornia Estoy de acuerdo que hay una pequeña posibilidad ApplyToDamage no funciona como se esperaba pero en el caso de ejemplo que da yo diría ApplyToDamage además necesita ser reelaborado para requerir pasarle el original sourceDamage también para que pueda calcular el debuf correctamente en esos casos. Para ser un pedante absoluto: en este punto, la información de dmg debe ser una estructura que incluya el dmg original, el dmg actual y la naturaleza de los daños, así como si los debufs tienen cosas como "vulnerabilidad al fuego". Por experiencia, no pasa mucho tiempo antes de que cualquier diseño de juego con debufs los exija.
  • 1 @StephaneHockenhull ¡bien dicho!

El código no parece crear un bucle infinito.

La única forma en que el bucle sería infinito sería si

debuf.ApplyToDamage(resolvedDamage); 

o

DestroyMe(); 

fueron a agregar nuevos elementos a la m_debufs envase.

Esto parece poco probable. Y si fuera el caso, el programa podría fallar al cambiar el contenedor mientras se itera.

Lo más probable es que el programa se bloquee debido a la llamada a DestroyMe(); que presumiblemente destruye el objeto actual que actualmente está ejecutando el bucle.

Podemos pensar en ello como la caricatura en la que el 'malo' corta una rama para que el 'bueno' caiga con ella, pero se da cuenta demasiado tarde de que está en el lado equivocado del corte. O la serpiente de Midgaard comiendo su propia cola.


También debo agregar que el síntoma más común de un bucle infinito es que congela el programa o hace que no responda. Bloqueará el programa si asigna memoria repetidamente, o hace algo que termina dividiendo por cero, o similares.


Basado en el comentario de Aki Tanaka,

Probablemente útil: captura de pantalla adicional de Umiko que dice que "estaba llamando a la misma operación una y otra vez", que puede que no se muestre en el código.

"Fue llamar a la misma operación una y otra vez" Esto es más probable.

Asumiendo que DestroyMe(); no está diseñado para ser llamado más de una vez, es más probable que cause un bloqueo.

Una forma de solucionar este problema sería cambiar el if para algo como esto:

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

Esto saldría del ciclo cuando el DestructibleActor sea destruido, asegurándose de que 1) el DestroyMe El método se llama solo una vez y 2) no aplique beneficios inútilmente una vez que el objeto ya se considera muerto.

2
  • 1 Salir del ciclo for cuando salud <= 0 es definitivamente una mejor solución que esperar hasta después del ciclo para verificar el estado.
  • Creo que probablemente break fuera del circuito, y entonces llamada DestroyMe(), Solo para estar seguros

Hay varios problemas con el código:

  1. Si no hay debufs, no se sufrirán daños.
  2. DestroyMe() el nombre de la función suena peligroso. Dependiendo de cómo se implemente, podría ser un problema o no. Si es solo una llamada al destructor del objeto actual envuelto en una función, entonces hay un problema, ya que el objeto se destruiría en medio de la ejecución del código. Si es una llamada a una función que pone en cola el evento de eliminación del objeto actual, entonces no hay problema, ya que el objeto sería destruido después de que complete su ejecución y el ciclo de eventos se active.
  3. El problema real que parece ser mencionado en el anime, el "Estaba llamando a la misma operación una y otra vez" - llamará DestroyMe() siempre y cuando m_currentHealth <= 0.f y quedan más debuffs por iterar, lo que podría resultar en DestroyMe() ser llamado varias veces, una y otra vez. El bucle debe detenerse después del primer DestroyMe() llamada, porque eliminar un objeto más de una vez da como resultado la corrupción de la memoria, lo que probablemente resultará en un bloqueo a largo plazo.

No estoy realmente seguro de por qué cada debuf quita la salud, en lugar de que la salud se quite solo una vez, con los efectos de todas las debuffs que se aplican al daño inicial recibido, pero asumiré que esa es la lógica correcta del juego.

El código correcto sería

// 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
  • Debo señalar que como he escrito asignadores de memoria en el pasado, eliminar la misma memoria no tiene por qué ser un problema. También podría ser redundante. Todo depende del comportamiento del asignador. El mío simplemente actuó como una lista vinculada de bajo nivel, por lo que el "nodo" para los datos eliminados se establece como libre varias veces o se vuelve a eliminar varias veces (lo que correspondería a redirecciones de puntero redundantes). Aunque es una buena captura.
  • Double-free es un error y generalmente conduce a un comportamiento indefinido y fallas. Incluso si tiene un asignador personalizado que de alguna manera no permite la reutilización de la misma dirección de memoria, el código doble libre es un código maloliente, ya que no tiene sentido y los analizadores de código estático le gritarán.
  • ¡Por supuesto! No lo diseñé para ese propósito. Algunos idiomas solo requieren un asignador debido a la falta de funciones. No no no. Simplemente estaba diciendo que un choque no está garantizado. Ciertas clasificaciones de diseño no siempre fallan.