AIMBOT 2.0
În episodul 1 din New Game 2, în jurul orei 9:40, există o fotografie a codului pe care Nene l-a scris:
Iată-l sub formă de text cu comentariile traduse:
// 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(); } } }
După împușcare, Umiko, arătând spre bucla for, a spus că motivul pentru care codul sa prăbușit este că există o buclă infinită.
Nu știu cu adevărat C ++, așa că nu sunt sigur dacă ceea ce spune ea este adevărat.
Din ceea ce văd, bucla for se repetă doar prin debufurile pe care Actorul le are în prezent. Cu excepția cazului în care Actorul are o cantitate infinită de debufs, nu cred că poate deveni o buclă infinită.
Dar nu sunt sigur, deoarece singurul motiv pentru care există o fotografie a codului este că au vrut să pună un ou de Paște aici, nu? Tocmai am fi luat o lovitură din spatele laptopului și l-am fi auzit pe Umiko spunând „Oh, ai o buclă infinită acolo”. Faptul că au arătat un anumit cod mă face să cred că cumva codul este un ou de Paște.
Codul va crea de fapt o buclă infinită?
8- Probabil util: captură de ecran suplimentară a lui Umiko spunând că „A fost apelând aceeași operație iar și iar ", care s-ar putea să nu fie afișat în cod.
- Oh! Nu știam asta! @AkiTanaka, sub-ul pe care l-am urmărit spune „buclă infinită”
- @LoganM Nu prea sunt de acord. Nu doar că OP are o întrebare despre un anumit cod sursă care provine dintr-un anime; Întrebarea OP se referă la o anumită declarație făcută despre codul sursă de către un personaj din anime și există un răspuns legat de anime, și anume „Crunchyroll a făcut gâfâit și a tradus greșit linia”.
- @senshin Cred că citești despre ce vrei să fie întrebarea, mai degrabă decât despre ce se pune de fapt. Întrebarea oferă un anumit cod sursă și întreabă dacă generează o buclă infinită ca cod C ++ din viața reală. Joc nou! este o operă fictivă; nu este nevoie ca codul prezentat în acesta să se conformeze standardelor din viața reală. Ceea ce spune Umiko despre cod este mai autoritar decât orice standard sau compilator C ++. Răspunsul de sus (acceptat) nu menționează nicio informație din univers. Cred că s-ar putea pune o întrebare pe această temă cu un răspuns bun, dar așa cum este formulat, nu este așa.
Codul nu este o buclă infinită, ci este un bug.
Există două (posibil trei) probleme:
- Dacă nu sunt prezente debufs, nu se vor aplica deloc daune
- Daunele excesive vor fi aplicate dacă există mai mult de 1 debuf
- Dacă DestroyMe () șterge imediat obiectul și mai sunt m_debufs de procesat, bucla se va executa peste un obiect șters și aruncă memoria. Majoritatea motoarelor de joc au o coadă de distrugere pentru a rezolva acest lucru și mai mult, ceea ce poate să nu fie o problemă.
Aplicarea daunelor ar trebui să fie în afara buclei.
Iată funcția corectată:
// 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 Suntem la Revizuirea Codului? : D
- 4 plutitoare sunt excelente pentru sănătate dacă nu depășești 16777216 CP. Puteți chiar să stabiliți sănătatea la infinit pentru a crea un inamic pe care îl puteți lovi, dar nu veți muri și să efectuați un atac cu o singură ucidere folosind daune infinite care totuși nu vor ucide un personaj infinit HP (rezultatul INF-INF este NaN), dar va ucide orice altceva. Deci este foarte util.
- 1 @cat Prin convenție în multe standarde de codare
m_
prefix înseamnă că este o variabilă membru. În acest caz, o variabilă membru aDestructibleActor
. - 2 @HotelCalifornia Sunt de acord că există puține șanse
ApplyToDamage
nu funcționează conform așteptărilor, dar în cazul de exemplu pe care îl dați aș spuneApplyToDamage
de asemenea trebuie refăcut pentru a necesita trecerea originaluluisourceDamage
de asemenea, astfel încât să poată calcula debuf în mod corespunzător în aceste cazuri. Pentru a fi un pedant absolut: în acest moment informațiile dmg ar trebui să fie o structură care să includă dmg-ul original, dmg-ul curent și natura daunelor (daunelor), de asemenea, dacă debufurile au lucruri precum „vulnerabilitatea la foc”. Din experiență, nu a trecut mult timp până când orice design de joc cu debufs le cere. - 1 @StephaneHockenhull bine spus!
Codul nu pare să creeze o buclă infinită.
Singurul mod în care bucla ar fi infinită ar fi dacă
debuf.ApplyToDamage(resolvedDamage);
sau
DestroyMe();
trebuiau să adauge elemente noi la m_debufs
container.
Acest lucru pare puțin probabil. Și dacă ar fi cazul, programul s-ar putea prăbuși din cauza schimbării containerului în timp ce va fi iterat.
Programul se va prăbuși cel mai probabil din cauza apelului către DestroyMe();
care probabil distruge obiectul curent care rulează în prezent bucla.
Ne putem gândi la acesta ca la desenul animat în care „tipul rău” vede o ramură pentru ca „tipul cel bun” să cadă cu el, dar își dă seama prea târziu că se află pe partea greșită a tăieturii. Sau Șarpele Midgaard mâncându-și propria coadă.
Ar trebui, de asemenea, să adaug că cel mai frecvent simptom al unei bucle infinite este că blochează programul sau îl face să nu răspundă. Se va bloca programul dacă alocă memorie în mod repetat sau face ceva care se termină împărțind la zero sau like-uri.
Pe baza comentariului lui Aki Tanaka,
Probabil util: captură de ecran suplimentară a lui Umiko spunând că „Se apelează la aceeași operație de mai multe ori”, care s-ar putea să nu fie afișată în cod.
„Se apelează la aceeași operație din nou și din nou” Acest lucru este mai probabil.
Asumand DestroyMe();
nu este conceput pentru a fi apelat de mai multe ori, este mai probabil să provoace un accident.
O modalitate de a rezolva această problemă ar fi schimbarea fișierului if
pentru așa ceva:
if (m_currentHealth <= 0.f) { m_currentHealth = 0.f; DestroyMe(); break; }
Aceasta va ieși din buclă atunci când DestructibleActor este distrus, asigurându-vă că 1) DestroyMe
metoda este numită o singură dată și 2) nu aplicați buff-uri inutil după ce obiectul este deja considerat mort.
- 1 Ieșirea din buclă for atunci când sănătatea <= 0 este cu siguranță o soluție mai bună decât așteptarea până după buclă pentru a verifica starea de sănătate.
- Cred că probabil aș fi făcut-o
break
din buclă și atunci apelDestroyMe()
, doar pentru a fi în siguranță
Există mai multe probleme cu codul:
- Dacă nu există debufs, nu ar fi suferit niciun prejudiciu.
DestroyMe()
numele funcției sună periculos. În funcție de modul în care este implementat, ar putea fi sau nu o problemă. Dacă este doar un apel către destructorul obiectului curent înfășurat într-o funcție, atunci există o problemă, deoarece obiectul ar fi distrus în mijlocul acestuia executând codul. Dacă este un apel către o funcție care pune în coadă evenimentul de ștergere a obiectului curent, atunci nu există nicio problemă, deoarece obiectul ar fi distrus după ce își va finaliza execuția și va începe bucla evenimentului.- Problema actuală care pare a fi menționată în anime, „A apelat la aceeași operație de mai multe ori” - va suna
DestroyMe()
atâta timp câtm_currentHealth <= 0.f
și mai sunt debuffs rămase de iterat, ceea ce ar putea duce laDestroyMe()
fiind chemat de mai multe ori, iar și iar. Bucla ar trebui să se oprească după primaDestroyMe()
apel, deoarece ștergerea unui obiect de mai multe ori are ca rezultat deteriorarea memoriei, ceea ce va duce probabil la blocarea pe termen lung.
Nu sunt foarte sigur de ce fiecare debuf elimină sănătatea, în loc să fie eliminată o singură dată, cu efectele tuturor debuffurilor aplicate asupra daunelor inițiale luate, dar voi presupune că aceasta este logica corectă a jocului.
Codul corect ar fi
// 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 - Ar trebui să subliniez că, deoarece am scris în trecut alocatori de memorie, ștergerea aceleiași memorii nu trebuie să fie o problemă. Ar putea fi, de asemenea, redundant. Totul depinde de comportamentul alocatorului. Al meu pur și simplu a acționat ca o listă legată de nivel scăzut, astfel încât „nodul” pentru datele șterse fie este setat ca liber de mai multe ori, fie re-șters de mai multe ori (ceea ce ar corespunde doar redirecționărilor de pointer redundante). O captură bună.
- Dublu-liber este o eroare și, în general, duce la un comportament nedefinit și blocări. Chiar dacă aveți un alocator personalizat care interzice cumva reutilizarea aceleiași adrese de memorie, dublu-liber este un cod urât mirositor, deoarece nu are sens și veți fi strigați de către analizorii de coduri statice.
- Desigur! Nu l-am proiectat în acest scop. Unele limbi necesită doar un alocator din cauza lipsei de caracteristici. Nu Nu NU. Afirmam doar că un avarie nu este garantat. Anumite clasificări de design nu se blochează întotdeauna.