Anonim

AIMBOT 2.0

Dans l'épisode 1 de New Game 2, vers 9h40, il y a une photo du code que Nene a écrit:

Le voici sous forme de texte avec les commentaires traduits:

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

Après le tir, Umiko, pointant la boucle for, a déclaré que la raison pour laquelle le code s'est écrasé est qu'il y a une boucle infinie.

Je ne connais pas vraiment le C ++, donc je ne suis pas sûr que ce qu'elle dit est vrai.

D'après ce que je peux voir, la boucle for ne fait qu'itérer les debufs que l'acteur possède actuellement. À moins que l'acteur n'ait une quantité infinie de débufs, je ne pense pas que cela puisse devenir une boucle infinie.

Mais je ne suis pas sûr parce que la seule raison pour laquelle il y a une copie du code est qu'ils voulaient mettre un œuf de Pâques ici, non? Nous aurions juste pris une photo de l'arrière de l'ordinateur portable et entendu Umiko dire "Oh, vous avez une boucle infinie là-bas". Le fait qu'ils aient montré du code me fait penser que le code est en quelque sorte un œuf de Pâques.

Le code créera-t-il réellement une boucle infinie?

8
  • Probablement utile: capture d'écran supplémentaire d'Umiko disant: "C'était appeler la même opération maintes et maintes fois ", ce qui pourrait ne pas apparaître dans le code.
  • Oh! Je ne savais pas ça! @AkiTanaka le sous-marin que j'ai regardé dit "boucle infinie"
  • @LoganM Je ne suis pas vraiment d'accord. Ce n'est pas seulement qu'OP a une question sur un code source qui vient d'un anime; La question d'OP porte sur une déclaration particulière à propos de le code source par un personnage dans l'anime, et il y a une réponse liée à l'anime, à savoir "Crunchyroll fait une gaffe et a mal traduit la ligne".
  • @senshin Je pense que vous lisez sur quoi vous voulez que la question porte, plutôt que sur ce qui est réellement posé. La question fournit du code source et demande s'il génère une boucle infinie en tant que code C ++ réel. Nouveau jeu! est une œuvre fictive; il n'est pas nécessaire que le code qui y soit présenté soit conforme aux normes de la vie réelle. Ce que dit Umiko à propos du code fait plus autorité que n'importe quel standard ou compilateur C ++. La réponse la plus élevée (acceptée) ne mentionne aucune information dans l'univers. Je pense qu'une question sur le sujet pourrait être posée à ce sujet avec une bonne réponse, mais tel que formulé, ce n'est pas ça.

Le code n'est pas une boucle infinie mais c'est un bogue.

Il y a deux (peut-être trois) problèmes:

  • Si aucun debufs n'est présent, aucun dommage ne sera appliqué du tout
  • Des dégâts excessifs seront appliqués s'il y a plus de 1 debuf
  • Si DestroyMe () supprime immédiatement l'objet et qu'il reste encore des m_debufs à traiter, la boucle s'exécutera sur un objet supprimé et effacera la mémoire. La plupart des moteurs de jeu ont une file d'attente de destruction pour contourner ce problème et plus encore, ce qui peut ne pas poser de problème.

L'application des dommages doit être en dehors de la boucle.

Voici la fonction corrigée:

// 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 Sommes-nous en révision de code? :RÉ
  • 4 flotteurs sont excellents pour la santé si vous ne dépassez pas 16777216 HP. Vous pouvez même définir la santé à l'infini pour créer un ennemi que vous pouvez toucher mais qui ne mourra pas, et avoir une attaque en un seul meurtre en utilisant des dégâts infinis qui ne tueront toujours pas un personnage HP infini (le résultat d'INF-INF est NaN) mais tuera tout le reste. C'est donc très utile.
  • 1 @cat Par convention dans de nombreuses normes de codage, m_ préfixe signifie que c'est une variable membre. Dans ce cas, une variable membre de DestructibleActor.
  • 2 @HotelCalifornia Je suis d'accord qu'il y a une petite chance ApplyToDamage ne fonctionne pas comme prévu mais dans l'exemple que vous donnez, je dirais ApplyToDamage également doit être retravaillé pour exiger le passage de l'original sourceDamage ainsi qu'il puisse calculer correctement le debuf dans ces cas. Pour être un pédant absolu: à ce stade, les informations de dmg devraient être une structure qui inclut le dmg original, le dmg actuel, et la nature des dommages aussi bien si les debufs ont des choses comme "vulnérabilité au feu". Par expérience, il n'y a pas longtemps avant que la conception de jeux avec debufs ne les demande.
  • 1 @StephaneHockenhull bien dit!

Le code ne semble pas créer une boucle infinie.

La seule façon dont la boucle serait infinie serait si

debuf.ApplyToDamage(resolvedDamage); 

ou

DestroyMe(); 

devaient ajouter de nouveaux éléments au m_debufs récipient.

Cela semble peu probable. Et si c'était le cas, le programme pourrait planter en raison du changement de conteneur lors de l'itération.

Le programme planterait très probablement en raison de l'appel à DestroyMe(); qui détruit vraisemblablement l'objet actuel qui exécute actuellement la boucle.

Nous pouvons le considérer comme le dessin animé où le «méchant» scie une branche pour faire tomber le «bon gars» avec, mais se rend compte trop tard qu'il est du mauvais côté de la coupe. Ou le serpent Midgaard mangeant sa propre queue.


Je dois également ajouter que le symptôme le plus courant d'une boucle infinie est qu'elle gèle le programme ou le rend non réactif. Il plantera le programme s'il alloue de la mémoire à plusieurs reprises, ou fait quelque chose qui finit par se diviser par zéro, ou des choses similaires.


Sur la base du commentaire d'Aki Tanaka,

Probablement utile: capture d'écran supplémentaire d'Umiko disant que "il appelait la même opération encore et encore", ce qui pourrait ne pas être affiché dans le code.

"Il appelait la même opération encore et encore" C'est plus probable.

En admettant que DestroyMe(); n'est pas conçu pour être appelé plus d'une fois, il est plus susceptible de provoquer un plantage.

Une façon de résoudre ce problème serait de modifier le if pour quelque chose comme ça:

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

Cela sortirait de la boucle lorsque le DestructibleActor est détruit, en s'assurant que 1) le DestroyMe La méthode n'est appelée qu'une seule fois et 2) n'applique pas de buffs inutilement une fois que l'objet est déjà considéré comme mort.

2
  • 1 Sortir de la boucle for lorsque la santé <= 0 est certainement une meilleure solution que d'attendre après la boucle pour vérifier la santé.
  • Je pense que j'aurais probablement break hors de la boucle, et alors appeler DestroyMe(), Juste pour être sûr

Il y a plusieurs problèmes avec le code:

  1. S'il n'y a pas de debufs, aucun dommage ne sera subi.
  2. DestroyMe() le nom de la fonction semble dangereux. Selon la façon dont il est mis en œuvre, cela peut ou non être un problème. S'il s'agit simplement d'un appel au destructeur de l'objet actuel encapsulé dans une fonction, alors il y a un problème, car l'objet serait détruit au milieu de l'exécution du code. S'il s'agit d'un appel à une fonction qui met en file d'attente l'événement de suppression de l'objet actuel, alors il n'y a pas de problème, car l'objet serait détruit après avoir terminé son exécution et la boucle d'événements démarre.
  3. Le problème réel qui semble être mentionné dans l'anime, le "Il appelait la même opération encore et encore" - il appellera DestroyMe() aussi longtemps que m_currentHealth <= 0.f et il reste plus d'affaiblissements à itérer, ce qui peut entraîner DestroyMe() être appelé plusieurs fois, encore et encore. La boucle doit s'arrêter après le premier DestroyMe() call, car la suppression d'un objet plus d'une fois entraîne une corruption de la mémoire, ce qui entraînera probablement un plantage à long terme.

Je ne sais pas vraiment pourquoi chaque debuf enlève la santé, au lieu que la santé ne soit supprimée qu'une seule fois, les effets de tous les affaiblissements étant appliqués sur les dégâts initiaux subis, mais je suppose que c'est la logique de jeu correcte.

Le code correct serait

// 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
  • Je dois souligner que comme j'ai écrit des allocateurs de mémoire dans le passé, la suppression de la même mémoire ne doit pas être un problème. Cela pourrait également être redondant. Tout dépend du comportement de l'allocateur. La mienne agissait simplement comme une liste chaînée de bas niveau, de sorte que le "nœud" des données supprimées soit défini comme libre plusieurs fois ou ré-supprimé plusieurs fois (ce qui correspondrait simplement à des redirections de pointeurs redondantes). Bonne prise cependant.
  • Double-free est un bogue et conduit généralement à un comportement indéfini et à des plantages. Même si vous avez un allocateur personnalisé qui interdit en quelque sorte la réutilisation de la même adresse mémoire, double-free est un code malodorant car cela n'a aucun sens et vous serez crié par les analyseurs de code statique.
  • Bien sûr! Je ne l'ai pas conçu dans ce but. Certaines langues nécessitent juste un allocateur en raison du manque de fonctionnalités. Non non Non. Je disais simplement qu'un crash n'est pas garanti. Certaines classifications de conception ne plantent pas toujours.