AIMBOT 2.0
W odcinku 1 Nowej Gry 2, około 9:40, jest ujęcie kodu napisanego przez Nene:
Oto jest w formie tekstowej z przetłumaczonymi komentarzami:
// 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(); } } }
Po strzale Umiko, wskazując na pętlę for, powiedział, że przyczyną awarii kodu jest nieskończona pętla.
Naprawdę nie znam C ++, więc nie jestem pewien, czy to, co mówi, jest prawdą.
Z tego, co widzę, pętla for po prostu przechodzi przez debufy, które aktualnie posiada aktor. O ile Aktor nie ma nieskończonej liczby debufów, nie sądzę, aby mogło to stać się nieskończoną pętlą.
Ale nie jestem pewien, ponieważ jedynym powodem, dla którego jest ujęcie kodu, jest to, że chcieli umieścić tutaj jajko wielkanocne, prawda? Po prostu zrobilibyśmy ujęcie z tyłu laptopa i usłyszelibyśmy Umiko mówiącą „Och, masz tam nieskończoną pętlę”. Fakt, że faktycznie pokazali jakiś kod, sprawia, że myślę, że w jakiś sposób jest on czymś w rodzaju pisanki.
Czy kod faktycznie utworzy nieskończoną pętlę?
8- Prawdopodobnie pomocne: dodatkowy zrzut ekranu Umiko z napisem „To było wywołując tę samą operację w kółko ”, co może nie być pokazane w kodzie.
- O! Nie wiedziałem tego! @AkiTanaka oglądany przeze mnie subwoofer mówi „nieskończona pętla”
- @LoganM Naprawdę się nie zgadzam. Nie chodzi tylko o to, że OP ma pytanie dotyczące kodu źródłowego, który przypadkiem pochodzi z anime; Pytanie OP dotyczy konkretnego oświadczenia o kod źródłowy przez postać w anime, a także odpowiedź związana z anime, a mianowicie „Crunchyroll zrobił głupotę i źle przetłumaczył wiersz”.
- @senshin Myślę, że czytasz raczej, o czym chcesz, aby dotyczyło to pytanie, a nie o to, co jest faktycznie zadawane. Pytanie zawiera kod źródłowy i pyta, czy generuje nieskończoną pętlę jako rzeczywisty kod C ++. Nowa gra! jest dziełem fikcyjnym; nie ma potrzeby, aby przedstawiony w nim kod był zgodny z rzeczywistymi standardami. To, co Umiko mówi o kodzie, jest bardziej autorytatywne niż jakiekolwiek standardy czy kompilatory C ++. Górna (zaakceptowana) odpowiedź nie zawiera żadnej wzmianki o jakichkolwiek informacjach z wszechświata. Myślę, że można by zadać na ten temat pytanie z dobrą odpowiedzią, ale tak nie jest.
Kod nie jest nieskończoną pętlą, ale jest to błąd.
Istnieją dwa (prawdopodobnie trzy) problemy:
- Jeśli nie ma żadnych debufów, żadne szkody nie zostaną zastosowane
- Nadmierne obrażenia zostaną zastosowane, jeśli jest więcej niż 1 debuf
- Jeśli DestroyMe () natychmiast usunie obiekt i nadal istnieją m_debufy do przetworzenia, pętla będzie wykonywana na usuniętym obiekcie i usunie pamięć. Większość silników gier ma kolejkę zniszczeń, aby obejść ten problem, a nawet więcej, więc może to nie stanowić problemu.
Nakładanie uszkodzeń powinno odbywać się poza pętlą.
Oto poprawiona funkcja:
// 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 Czy jesteśmy na przeglądzie kodu? :RE
- 4 pływaki są świetne dla zdrowia, jeśli nie przekroczysz 16777216 PW. Możesz nawet ustawić zdrowie na nieskończone, aby stworzyć wroga, którego możesz trafić, ale nie zginie, i wykonać atak polegający na jednym zabiciu, zadając nieskończone obrażenia, które nadal nie zabiją postaci o nieskończonej HP (wynikiem INF-INF jest NaN), ale zabije wszystko inne. Więc jest to bardzo przydatne.
- 1 @cat Zgodnie z konwencją w wielu standardach kodowania
m_
przedrostek oznacza, że jest to zmienna składowa. W tym przypadku zmienna składowaDestructibleActor
. - 2 @HotelCalifornia Zgadzam się, że jest mała szansa
ApplyToDamage
nie działa zgodnie z oczekiwaniami, ale w przykładzie, który podasz, powiedziałbymApplyToDamage
również należy przerobić, aby wymagać przekazania oryginałusourceDamage
a także, aby mógł prawidłowo obliczyć debuf w takich przypadkach. Aby być absolutnym pedantem: w tym momencie informacja o dmg powinna być strukturą, która zawiera oryginalny dmg, bieżący dmg i rodzaj obrażeń, jak również, jeśli debufy mają coś takiego jak "podatność na ogień". Z doświadczenia wynika, że niedługo jakikolwiek projekt gry z debufami będzie tego wymagał. - 1 @StephaneHockenhull dobrze powiedziane!
Wydaje się, że kod nie tworzy nieskończonej pętli.
Jedynym sposobem, w jaki pętla byłaby nieskończona, byłoby jeśli
debuf.ApplyToDamage(resolvedDamage);
lub
DestroyMe();
miały dodać nowe pozycje do m_debufs
pojemnik.
Wydaje się to mało prawdopodobne. Gdyby tak było, program mógłby się zawiesić z powodu zmiany kontenera podczas iteracji.
Program najprawdopodobniej zawiesiłby się z powodu wywołania DestroyMe();
co przypuszczalnie niszczy bieżący obiekt, który aktualnie uruchamia pętlę.
Możemy myśleć o tym jak o kreskówce, w której „zły facet” widzi gałąź, aby „dobry facet” spadł z nią, ale zbyt późno zdajemy sobie sprawę, że jest po złej stronie cięcia. Albo Wąż Midgaard zjadający własny ogon.
Powinienem też dodać, że najczęstszym objawem nieskończonej pętli jest to, że zawiesza ona program lub powoduje, że nie reaguje. Spowoduje to awarię programu, jeśli przydzieli pamięć wielokrotnie, lub zrobi coś, co zakończy się podzieleniem przez zero lub podobnymi.
Na podstawie komentarza Aki Tanaki,
Prawdopodobnie pomocne: dodatkowy zrzut ekranu Umiko mówiący, że „wywoływał tę samą operację w kółko”, co może nie być pokazane w kodzie.
„Powtarzał tę samą operację” To jest bardziej prawdopodobne.
Przy założeniu, że DestroyMe();
nie jest przeznaczony do wywołania więcej niż raz, jest bardziej prawdopodobne, że spowoduje awarię.
Sposobem na rozwiązanie tego problemu byłaby zmiana if
na coś takiego:
if (m_currentHealth <= 0.f) { m_currentHealth = 0.f; DestroyMe(); break; }
Spowoduje to wyjście z pętli, gdy DestructibleActor zostanie zniszczony, upewniając się, że 1) DestroyMe
metoda jest wywoływana tylko raz i 2) nie stosuj buffów bezużytecznie, gdy obiekt jest już uznany za martwy.
- 1 Wyjście z pętli for, gdy zdrowie <= 0, jest zdecydowanie lepszym rozwiązaniem niż czekanie do końca pętli, aby sprawdzić stan.
- Myślę, że prawdopodobnie
break
poza pętlą i następnie połączenieDestroyMe()
, żeby być bezpiecznym
Istnieje kilka problemów z kodem:
- Jeśli nie ma debufów, żadne szkody nie zostałyby odniesione.
DestroyMe()
nazwa funkcji brzmi niebezpiecznie. W zależności od tego, jak jest zaimplementowany, może to stanowić problem lub nie. Jeśli jest to tylko wywołanie destruktora bieżącego obiektu opakowanego w funkcję, to występuje problem, ponieważ obiekt zostałby zniszczony w trakcie wykonywania kodu. Jeśli jest to wywołanie funkcji, która kolejkuje zdarzenie usunięcia bieżącego obiektu, nie ma problemu, ponieważ obiekt zostałby zniszczony po zakończeniu wykonywania i uruchomieniu pętli zdarzeń.- Faktyczny problem, który wydaje się być poruszany w anime, to „Ciągłe wywoływanie tej samej operacji” - wywoła
DestroyMe()
tak długo jakm_currentHealth <= 0.f
i jest jeszcze więcej debuffów do iteracji, co może skutkowaćDestroyMe()
wzywano go wiele razy, w kółko. Pętla powinna się zatrzymać po pierwszejDestroyMe()
wywołanie, ponieważ usunięcie obiektu więcej niż jeden raz powoduje uszkodzenie pamięci, co prawdopodobnie spowoduje awarię na dłuższą metę.
Nie jestem do końca pewien, dlaczego każdy debuf odbiera zdrowie, a nie tylko raz, a efekty wszystkich debuffów są nakładane na początkowe otrzymane obrażenia, ale zakładam, że jest to właściwa logika gry.
Prawidłowy kod to
// 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 - Powinienem zwrócić uwagę, że ponieważ pisałem w przeszłości alokatory pamięci, usunięcie tej samej pamięci nie musi być problemem. Może być również zbędny. Wszystko zależy od zachowania alokatora. Moja po prostu działała jak lista połączona niskiego poziomu, więc „węzeł” dla usuniętych danych zostaje kilkakrotnie zwolniony lub kilkakrotnie ponownie usunięty (co odpowiadałoby po prostu redundantnym przekierowaniom wskaźnika). Dobry chwyt.
- Double-free to błąd, który generalnie prowadzi do niezdefiniowanego zachowania i awarii. Nawet jeśli masz niestandardowy alokator, który w jakiś sposób uniemożliwia ponowne użycie tego samego adresu pamięci, podwójne wolne jest śmierdzącym kodem, ponieważ nie ma to sensu i zostaniesz wykrzyczany przez statyczne analizatory kodu.
- Oczywiście! Nie zaprojektowałem tego w tym celu. Niektóre języki wymagają tylko alokatora ze względu na brak funkcji. Nie nie nie. Stwierdziłem tylko, że awaria nie jest gwarantowana. Niektóre klasyfikacje projektów nie zawsze ulegają awarii.