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(); } } }
Після пострілу Уміко, вказуючи на цикл for, сказав, що причиною того, що код розбився, є те, що існує нескінченний цикл.
Я насправді не знаю C ++, тому я не впевнений, чи правда те, що вона говорить.
З того, що я бачу, цикл for просто перебирає налагодження, які зараз має Актор. Якщо у Актора немає нескінченної кількості налагоджень, я не думаю, що це може стати нескінченною петлею.
Але я не впевнений, тому що єдина причина того, що з'явився знімок коду, полягає в тому, що вони хотіли поставити сюди пасхальне яйце, так? Ми б просто отримали знімок задньої панелі ноутбука і почули б, як Уміко сказав: "О, у вас там нескінченна петля". Той факт, що вони насправді показали якийсь код, змушує мене думати, що якимось чином код є якимось писанкою.
Чи насправді код створить нескінченний цикл?
8- Можливо, корисно: додатковий знімок екрану Уміко, який каже: "Це було виклик тієї ж операції знову і знову ", що може не відображатися в коді.
- О! Я цього не знав! @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 Ми переглядаємо код? : D
- 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();
який, імовірно, знищує поточний об'єкт, який зараз виконує цикл.
Ми можемо сприймати це як мультфільм, де "поганий хлопець" пилить гілку, щоб "добрий хлопець" впав разом із ним, але занадто пізно розуміє, що опинився на неправильній стороні зрізу. Або Змія Мідгаард, яка їсть власний хвіст.
Слід також додати, що найпоширенішим симптомом нескінченного циклу є те, що він заморожує програму або робить її нечутливою. Це призведе до аварійного завершення роботи програми, якщо вона виділить пам'ять неодноразово, або зробить щось, що закінчиться діленням на нуль, або лайки.
На основі коментаря Акі Танаки,
Можливо, корисно: додатковий знімок екрану Umiko, який каже, що "Це викликало ту саму операцію знову і знову", яка може не відображатися в коді.
"Це викликало одну і ту ж операцію знову і знову" Це швидше за все.
Якщо припустити, що DestroyMe();
не призначений для виклику більше одного разу, це, швидше за все, спричинить збій.
Способом вирішення цієї проблеми було б змінити if
приблизно для цього:
if (m_currentHealth <= 0.f) { m_currentHealth = 0.f; DestroyMe(); break; }
Це призведе до виходу з циклу, коли DestructibleActor буде знищено, переконавшись, що 1) DestroyMe
метод викликається лише один раз і 2) не застосовувати баффи марно, коли об'єкт вже вважається мертвим.
- 1 Вирватися з циклу for, коли стан здоров'я <= 0, безумовно, є кращим виправленням, ніж чекати, поки не закінчиться цикл для перевірки стану.
- Я думаю, що мабуть
break
з циклу, і потім дзвінокDestroyMe()
, просто щоб перестрахуватися
Існує кілька проблем із кодом:
- Якщо немає налагоджень, шкода не буде спричинена.
DestroyMe()
назва функції звучить небезпечно. Залежно від того, як це реалізовано, це може бути проблемою чи не. Якщо це просто виклик деструктора поточного об'єкта, загорнутого у функцію, тоді виникає проблема, оскільки об'єкт буде знищений в середині виконання коду. Якщо це виклик функції, яка ставить у чергу подію видалення поточного об'єкта, тоді не виникає жодної проблеми, оскільки об'єкт буде знищений після завершення його виконання та запуску циклу подій.- Фактична проблема, про яку, здається, згадується в аніме, "Це викликало ту саму операцію знову і знову" - вона буде викликати
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 - це помилка, яка, як правило, призводить до невизначеної поведінки та збоїв. Навіть якщо у вас є спеціальний розподільник, який якимось чином забороняє повторне використання однієї і тієї ж адреси пам'яті, подвійний вільний - це смердючий код, оскільки він не має сенсу, і на вас будуть кричати статичні аналізатори коду.
- Звичайно! Я не розробляв його для цієї мети. Деякі мови просто потребують розподілювача через відсутність функцій. Ні-ні-ні. Я просто заявив, що аварія не гарантована. Певні класифікації дизайну не завжди падають.