Anonim

AIMBOT 2.0

В епизод 1 на Нова игра 2, около 9:40, има изстрел на кода, който Нене е написала:

Ето го в текстова форма с преведени коментари:

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

След изстрела Umiko, сочейки към цикъла for, каза, че причината, поради която кодът се е разбил, е, че има безкраен цикъл.

Наистина не познавам C ++, така че не съм сигурен дали това, което тя казва е вярно.

От това, което виждам, цикълът for е просто итерация през debufs, които Актьорът има в момента. Освен ако Актьорът има безкрайно количество дебютове, не мисля, че може да се превърне в безкраен цикъл.

Но не съм сигурен, защото единствената причина, поради която има изстрел на кода, е, че те искаха да сложат великденско яйце тук, нали? Току-що щяхме да получим снимка на гърба на лаптопа и да чуем Умико да казва „О, имаш безкраен цикъл там“. Фактът, че всъщност са показали някакъв код, ме кара да мисля, че по някакъв начин кодът е някакво великденско яйце.

Кодът всъщност ще създаде ли безкраен цикъл?

8
  • Вероятно полезно: допълнителна екранна снимка на Umiko, която казва, че „Беше извикване на същата операция отново и отново ", което може да не се показва в кода.
  • О! Не знаех това! @AkiTanaka в подгрупата, която гледах, казва "безкраен цикъл"
  • @LoganM Наистина не съм съгласен. Не само, че OP има въпрос за някакъв изходен код, който случайно идва от аниме; Въпросът на ОП е относно конкретно направено изявление относно изходния код от символ в анимето и има отговор, свързан с аниме, а именно „Crunchyroll направен с крив и неправилен превод на реда“.
  • @senshin Мисля, че четете това, за което искате да бъде въпросът, а не това, което всъщност е зададено. Въпросът предоставя някакъв изходен код и пита дали генерира безкраен цикъл като C ++ код в реалния живот. Нова игра! е измислена творба; няма нужда кодът, представен в него, да отговаря на реалните стандарти. Това, което Umiko казва за кода, е по-авторитетно от всички C ++ стандарти или компилатори. Най-отгореният (приет) отговор не споменава никаква информация във вселената. Мисля, че по този въпрос може да се зададе въпрос с добър отговор, но както е формулиран, това не е така.

Кодът не е безкраен цикъл, но е грешка.

Има два (евентуално три) проблема:

  • Ако липсват дебюти, изобщо няма да бъдат приложени щети
  • Прекалено големи щети ще бъдат приложени, ако има повече от 1 дебаф
  • Ако DestroyMe () незабавно изтрие обекта и все още има m_debufs за обработка, цикълът ще се изпълни върху изтрит обект и ще изхвърли паметта. Повечето игрални механизми имат опашка за унищожаване, за да заобиколят това и още повече, така че това може да не е проблем.

Прилагането на щети трябва да е извън цикъла.

Ето коригираната функция:

// 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 Ние сме на преглед на кода? :Д
  • 4 плувки са чудесни за здравето, ако не се изкачите над 16777216 HP. Можете дори да настроите здравето на безкрайно, за да създадете враг, който можете да ударите, но няма да умре, и да извършите атака с едно убийство, като използвате безкрайни щети, които все още няма да убият безкраен характер на HP (резултатът от INF-INF е NaN), но ще убие всичко останало. Така че е много полезно.
  • 1 @cat По конвенция в много стандарти за кодиране m_ префикс означава, че е променлива член. В този случай променлива член на DestructibleActor.
  • 2 @HotelCalifornia Съгласен съм, че има малък шанс ApplyToDamage не работи според очакванията, но в примера, който давате, бих казал ApplyToDamage също трябва да се преработи, за да се наложи предаването му в оригинал sourceDamage както и за да може да изчисли дебюфа правилно в тези случаи. За да бъдете абсолютен педант: в този момент информацията за dmg трябва да бъде структура, която включва оригиналния dmg, текущия dmg и естеството на щетите, както и ако дебъфите имат неща като "уязвимост към пожар". От опит не след дълго всеки игрален дизайн с дебюфове изисква това.
  • 1 @StephaneHockenhull добре казано!

Изглежда, че кодът не създава безкраен цикъл.

Единственият начин цикълът да бъде безкраен би бил, ако

debuf.ApplyToDamage(resolvedDamage); 

или

DestroyMe(); 

трябваше да добавите нови елементи към m_debufs контейнер.

Това изглежда малко вероятно. И ако беше така, програмата можеше да се срине поради промяна на контейнера, докато беше итерирана.

Програмата най-вероятно ще се срине поради обаждането DestroyMe(); което вероятно унищожава текущия обект, който в момента изпълнява цикъла.

Можем да го възприемем като карикатурата, в която „лошият“ вижда клон, за да падне „добрият“ с него, но осъзнава твърде късно, че е от грешната страна на среза. Или Midgaard Snake, която яде собствената си опашка.


Трябва също да добавя, че най-честият симптом на безкраен цикъл е, че той замразява програмата или я кара да не реагира. Ще срине програмата, ако разпределя памет многократно или прави нещо, което в крайна сметка се дели на нула, или харесванията.


Въз основа на коментара на Аки Танака,

Вероятно полезно: допълнителна екранна снимка на Umiko, която казва, че „Извикваше една и съща операция отново и отново“, която може да не се показва в кода.

„Викаше една и съща операция отново и отново“ Това е по-вероятно.

Ако приемем това DestroyMe(); не е проектиран да бъде извикан повече от веднъж, има по-голяма вероятност да причини срив.

Начин за отстраняване на този проблем би бил промяната на if за нещо подобно:

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

Това ще излезе от цикъла, когато DestructibleActor бъде унищожен, като се уверите, че 1) DestroyMe метод се извиква само веднъж и 2) не прилагайте безполезно буфове, след като обектът вече се счита за мъртъв.

2
  • 1 Прекъсването на цикъла for, когато здравето <= 0 определено е по-добро решение, отколкото изчакването до цикъла за проверка на здравето.
  • Мисля, че вероятно бих break извън цикъла и тогава обади се DestroyMe(), само за да съм в безопасност

Има няколко проблема с кода:

  1. Ако няма дебауфи, няма да бъдат нанесени щети.
  2. DestroyMe() името на функцията звучи опасно. В зависимост от начина, по който се прилага, това може да е проблем, или не. Ако това е само извикване на деструктора на текущия обект, увит във функция, тогава има проблем, тъй като обектът ще бъде унищожен в средата на неговия изпълняващ код. Ако това е извикване на функция, която поставя на опашка събитието за изтриване на текущия обект, тогава няма проблем, тъй като обектът ще бъде унищожен, след като завърши изпълнението си и цикълът на събитията започне.
  3. Действителният проблем, който изглежда е споменат в анимето, „Той извикваше една и съща операция отново и отново“ - той ще извиква DestroyMe() стига m_currentHealth <= 0.f и има още дебъфи, оставени за итерация, което може да доведе до DestroyMe() да се извиква няколко пъти, отново и отново. Цикълът трябва да спре след първия DestroyMe() повикване, защото изтриването на обект повече от веднъж води до повреда на паметта, което вероятно ще доведе до срив в дългосрочен план.

Не съм наистина сигурен защо всеки дебюф отнема здравето, вместо здравето да се отнема само веднъж, като ефектите от всички дебъфи се прилагат върху първоначално взетите щети, но ще предположа, че това е правилната логика на играта.

Правилният код ще бъде

// 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
  • Трябва да отбележа, че както съм писал разпределители на паметта в миналото, изтриването на същата памет не трябва да е проблем. Той също може да бъде излишен. Всичко зависи от поведението на разпределителя. Моят просто действаше като свързан списък с ниско ниво, така че „възелът“ за изтритите данни или се освобождава няколко пъти, или се пределетира няколко пъти (което просто би съответствало на излишни пренасочвания на указатели). Добър улов все пак.
  • Double-free е грешка и обикновено води до недефинирано поведение и сривове. Дори ако имате персонализиран разпределител, който по някакъв начин забранява повторното използване на един и същ адрес в паметта, двойното освобождаване е миризлив код, тъй като няма смисъл и ще ви се викат от статичните анализатори на кода.
  • Разбира се! Не съм го проектирал за тази цел. Някои езици просто изискват разпределител поради липса на функции. Не не не. Просто заявявах, че катастрофата не е гарантирана. Някои класификации на дизайна не винаги се сриват.