Anonim

AIMBOT 2.0

I avsnitt 1 av Nytt spel 2, runt 9:40, finns det ett skott av koden som Nene har skrivit:

Här är det i textform med översatta kommentarer:

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

Efter skottet sade Umiko, som pekade på for-slingan, att anledningen till att koden kraschade är att det finns en oändlig slinga.

Jag vet inte riktigt C ++ så jag är inte säker på om det hon säger är sant.

Enligt vad jag kan se, går it-slingan bara igenom de debufs som skådespelaren för närvarande har. Om inte skådespelaren har en oändlig mängd debufs, tror jag inte att det möjligen kan bli en oändlig slinga.

Men jag är inte säker, för den enda anledningen till att det finns ett skott av koden är att de ville sätta ett påskägg här, eller hur? Vi skulle precis ha fått ett skott på baksidan av den bärbara datorn och hört Umiko säga "Åh du har en oändlig slinga där". Det faktum att de faktiskt visade lite kod får mig att tro att koden på något sätt är ett påskägg av något slag.

Kommer koden att skapa en oändlig slinga?

8
  • Troligen bra: ytterligare skärmdump av Umiko som säger att "Det var ringer samma operation om och om igen ", som kanske inte visas i koden.
  • åh! Jag visste inte det! @AkiTanaka suben som jag såg säger "oändlig slinga"
  • @LoganM Jag håller inte riktigt med. Det är inte bara att OP har en fråga om någon källkod som råkar komma från en anime; OP: s fråga handlar om ett särskilt uttalande handla om källkoden av en karaktär i anime, och det finns ett anime-relaterat svar, nämligen "Crunchyroll gjort fånigt och felöversatt linjen".
  • @senshin Jag tror att du läser vad du vill att frågan ska handla om, snarare än vad som faktiskt ställs. Frågan ger viss källkod och frågar om den genererar en oändlig slinga som verklig C ++ - kod. Nytt spel! är ett fiktivt verk; det finns inget behov av kod som presenteras i den för att överensstämma med verkliga normer. Vad Umiko säger om koden är mer auktoritativ än någon C ++ - standard eller kompilator. Det översta (accepterade) svaret nämner ingen information i universum. Jag tror att en ämnesfråga kan ställas om detta med ett bra svar, men så uttryckt är det inte det.

Koden är inte en oändlig slinga men det är en bugg.

Det finns två (möjligen tre) problem:

  • Om det inte finns några debufs kommer ingen skada att tillämpas alls
  • Överdriven skada kommer att tillämpas om det finns mer än 1 debuf
  • Om DestroyMe () omedelbart tar bort objektet och det fortfarande finns m_debufs som ska bearbetas kommer slingan att köras över ett borttaget objekt och skräpminne. De flesta spelmotorer har en förstörningskö för att kringgå detta och mer så det kanske inte är ett problem.

Tillämpningen av skador bör vara utanför slingan.

Här är den korrigerade funktionen:

// 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 Är vi på kodgranskning? : D
  • 4 flottörer är bra för hälsan om du inte går över 16777216 HP. Du kan till och med ställa in hälsan till oändlig för att skapa en fiende du kan slå men inte dö och ha en en-dödsattack med oändlig skada som fortfarande inte dödar en oändlig HP-karaktär (resultatet av INF-INF är NaN) kommer att döda allt annat. Så det är väldigt användbart.
  • 1 @cat Enligt konvention i många kodningsstandarder m_ prefix betyder att det är en medlemsvariabel. I detta fall en medlemsvariabel av DestructibleActor.
  • 2 @HotelCalifornia Jag håller med om att det finns en liten chans ApplyToDamage fungerar inte som förväntat men i det exempel du ger skulle jag säga ApplyToDamage också måste omarbetas för att kräva att originalet skickas sourceDamage också så att den kan beräkna debuf ordentligt i dessa fall. För att vara en absolut pedant: vid denna tidpunkt bör dmg-informationen vara en struktur som inkluderar den ursprungliga dmg, nuvarande dmg och skadans (arnas) natur även om debufs har saker som "sårbarhet för eld". Av erfarenhet dröjer det inte länge innan någon speldesign med debufs kräver dessa.
  • 1 @StephaneHockenhull väl sagt!

Koden verkar inte skapa en oändlig slinga.

Det enda sättet som slingan skulle vara oändlig skulle vara om

debuf.ApplyToDamage(resolvedDamage); 

eller

DestroyMe(); 

skulle lägga till nya artiklar i m_debufs behållare.

Detta verkar osannolikt. Och om så vore fallet kunde programmet krascha på grund av att behållaren byttes medan den upprepades.

Programmet skulle sannolikt krascha på grund av uppmaningen till DestroyMe(); vilket förmodligen förstör det aktuella objektet som för närvarande kör loop.

Vi kan tänka på det som en tecknad film där den "dåliga killen" sågar en gren för att den "goda killen" ska falla med den, men inser för sent att han är på fel sida av snittet. Eller Midgaard Snake som äter sin egen svans.


Jag skulle också vilja tillägga att det vanligaste symptomet på en oändlig slinga är att det fryser programmet eller gör det inte lyhört. Det kommer att krascha programmet om det tilldelas minne upprepade gånger, eller gör något som slutar dela med noll, eller liknande.


Baserat på kommentaren från Aki Tanaka,

Förmodligen användbart: ytterligare skärmdump av Umiko som säger att "Det kallade samma operation om och om igen", som kanske inte visas i koden.

"Det kallade samma operation om och om igen" Detta är mer troligt.

Antar det DestroyMe(); är inte utformad för att kallas mer än en gång, det är mer sannolikt att det orsakar en krasch.

Ett sätt att åtgärda problemet skulle vara att ändra if för något liknande detta:

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

Detta skulle lämna slingan när DestructibleActor förstörs och se till att 1) DestroyMe metoden kallas bara en gång och 2) använd inte buffs värdelöst när objektet redan anses dött.

2
  • 1 Att bryta ut för-slingan när hälsa <= 0 är definitivt en bättre fix än att vänta till efter slingan för att kontrollera hälsan.
  • Jag tror nog break ur slingan, och sedan ring upp DestroyMe(), bara för att vara säker

Det finns flera problem med koden:

  1. Om det inte finns några debufs skulle ingen skada tas.
  2. DestroyMe() funktionsnamn låter farligt. Beroende på hur det implementeras kan det vara ett problem. Om det bara är ett samtal till destruktorn för det aktuella objektet som är insvept i en funktion, finns det ett problem, eftersom objektet skulle förstöras mitt i det att koden körs. Om det är ett anrop till en funktion som köer borttagningshändelsen för det aktuella objektet, så är det inget problem, eftersom objektet skulle förstöras när det har slutfört körningen och händelseslingan sparkar in.
  3. Det faktiska problemet som verkar nämnas i anime, "Det kallade samma operation om och om igen" - det kommer att ringa DestroyMe() så länge som m_currentHealth <= 0.f och det finns fler debuffer kvar att iterera, vilket kan leda till DestroyMe() kallas flera gånger, om och om igen. Slingan ska sluta efter den första DestroyMe() ring, eftersom att ta bort ett objekt mer än en gång resulterar i minneskorruption, vilket sannolikt kommer att resultera i en krasch på lång sikt.

Jag är inte riktigt säker på varför varje debuf tar bort hälsan, istället för att hälsan tas bort bara en gång, med effekterna av att alla debuffer tillämpas på den initiala skadan, men jag antar att det är rätt spellogik.

Den korrekta koden skulle vara

// 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
  • Jag bör påpeka att eftersom jag tidigare har skrivit minnesfördelare behöver det inte vara något problem att ta bort samma minne. Det kan också vara överflödigt. Allt beror på fördelarens beteende. Gruvan fungerade bara som en länkad lista på låg nivå så att "noden" för de raderade data antingen blir fri flera gånger eller omplaceras flera gånger (vilket bara motsvarar redundanta pekaren omdirigeringar). Bra fångst men.
  • Dubbelfri är ett fel och leder i allmänhet till odefinierat beteende och kraschar. Även om du har en anpassad fördelare som på något sätt tillåter återanvändning av samma minnesadress, är dubbelfri en illaluktande kod eftersom det inte är meningsfullt och du kommer att skrikas på av statiska kodanalysatorer.
  • Självklart! Jag designade det inte för det ändamålet. Vissa språk kräver bara en fördelare på grund av brist på funktioner. Nej nej nej. Jag sa bara att en krasch inte garanteras. Vissa designklassificeringar kraschar inte alltid.