AIMBOT 2.0
Trong tập 1 của New Game 2, vào khoảng 9:40, có một đoạn mã mà Nene đã viết:
Đây là nó ở dạng văn bản với các nhận xét được dịch:
// 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(); } } }
Sau cảnh quay, Umiko, chỉ vào vòng lặp for, nói rằng lý do tại sao đoạn mã bị lỗi là có một vòng lặp vô hạn.
Tôi không thực sự biết C ++ nên tôi không chắc liệu những gì cô ấy đang nói có đúng không.
Từ những gì tôi có thể thấy, vòng lặp for chỉ đang lặp lại qua các debufs mà Actor hiện có. Trừ khi Actor có số lượng debufs vô hạn, tôi không nghĩ nó có thể trở thành một vòng lặp vô hạn.
Nhưng tôi không chắc vì lý do duy nhất mà có một đoạn mã là họ muốn đặt một quả trứng Phục sinh ở đây, phải không? Chúng tôi vừa có một bức ảnh chụp mặt sau của máy tính xách tay và nghe thấy Umiko nói "Ồ, bạn có một vòng lặp vô hạn ở đó". Thực tế là họ thực sự cho thấy một số mã khiến tôi nghĩ rằng bằng cách nào đó mã là một quả trứng Phục sinh của một loại nào đó.
Mã sẽ thực sự tạo ra một vòng lặp vô hạn?
8- Có lẽ hữu ích: ảnh chụp màn hình bổ sung của Umiko nói rằng "Đó là gọi cùng một hoạt động lặp đi lặp lại ", điều này có thể không được hiển thị trong mã.
- Oh! Tôi không biết điều đó! @AkiTanaka phụ mà tôi đã xem nói "vòng lặp vô hạn"
- @LoganM Tôi không thực sự đồng ý. Không chỉ là OP có thắc mắc về một số mã nguồn tình cờ đến từ anime; Câu hỏi của OP là về một tuyên bố cụ thể được đưa ra trong khoảng mã nguồn của một nhân vật trong anime, và có một câu trả lời liên quan đến anime, đó là "Crunchyroll đã thực hiện một cách ngu ngốc và dịch sai dòng".
- @senshin Tôi nghĩ bạn đang đọc những gì bạn muốn câu hỏi nói về, hơn là những gì thực sự được hỏi. Câu hỏi cung cấp một số mã nguồn và hỏi liệu nó có tạo ra một vòng lặp vô hạn như mã C ++ ngoài đời thực hay không. Trò chơi mới! là một tác phẩm hư cấu; không cần mã được trình bày trong đó để phù hợp với các tiêu chuẩn trong cuộc sống thực. Những gì Umiko nói về mã có thẩm quyền hơn bất kỳ tiêu chuẩn hoặc trình biên dịch C ++ nào. Câu trả lời hàng đầu (được chấp nhận) không đề cập đến bất kỳ thông tin nào trong vũ trụ. Tôi nghĩ rằng một câu hỏi chủ đề có thể được hỏi về điều này với một câu trả lời tốt, nhưng như đã diễn giải thì đây không phải là nó.
Mã không phải là một vòng lặp vô hạn mà nó là một lỗi.
Có hai (có thể là ba) vấn đề:
- Nếu không có debufs thì không có thiệt hại nào được áp dụng
- Sát thương quá mức sẽ được áp dụng nếu có nhiều hơn 1 lần gỡ lỗi
- Nếu DestroyMe () ngay lập tức xóa đối tượng và vẫn còn m_debufs được xử lý thì vòng lặp sẽ thực thi trên đối tượng đã xóa và bộ nhớ chuyển vào thùng rác. Hầu hết các công cụ trò chơi đều có hàng đợi tiêu diệt để giải quyết vấn đề này và hơn thế nữa để điều đó có thể không phải là vấn đề.
Ứng dụng của thiệt hại phải nằm ngoài vòng lặp.
Đây là chức năng đã sửa:
// 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 Chúng tôi có đang xem xét mã không? : D
- 4 phao là rất tốt cho sức khỏe nếu bạn không vượt quá 16777216 HP. Bạn thậm chí có thể đặt sức khỏe thành vô hạn để tạo ra một kẻ thù mà bạn có thể đánh nhưng không chết và có một cuộc tấn công một sát thương bằng cách sử dụng sát thương vô hạn mà vẫn không giết một nhân vật có HP vô hạn (kết quả của INF-INF là NaN) nhưng sẽ giết mọi thứ khác. Vì vậy, nó rất hữu ích.
- 1 @cat Theo quy ước trong nhiều tiêu chuẩn mã hóa,
m_
tiền tố có nghĩa là nó là một biến thành viên. Trong trường hợp này, một biến thành viên củaDestructibleActor
. - 2 @HotelCalifornia Tôi đồng ý rằng có một cơ hội nhỏ
ApplyToDamage
không hoạt động như mong đợi nhưng trong trường hợp ví dụ bạn đưa ra, tôi sẽ nóiApplyToDamage
cũng thế cần được làm lại để yêu cầu chuyển nó như ban đầusourceDamage
cũng như để nó có thể tính toán debuf đúng trong những trường hợp đó. Để trở thành nguồn gốc tuyệt đối: tại thời điểm này, thông tin dmg phải là một cấu trúc bao gồm dmg ban đầu, dmg hiện tại và bản chất của (các) thiệt hại cũng như nếu các bản gỡ lỗi có những thứ như "lỗ hổng bảo vệ khi cháy". Theo kinh nghiệm, không lâu trước khi bất kỳ thiết kế trò chơi nào có gỡ lỗi đều yêu cầu những điều này. - 1 @StephaneHockenhull đã nói rất hay!
Đoạn mã dường như không tạo ra một vòng lặp vô hạn.
Cách duy nhất để vòng lặp là vô hạn là nếu
debuf.ApplyToDamage(resolvedDamage);
hoặc là
DestroyMe();
đã thêm các mục mới vào m_debufs
thùng đựng hàng.
Điều này có vẻ khó xảy ra. Và nếu đúng như vậy, chương trình có thể gặp sự cố do thay đổi vùng chứa trong khi được lặp lại.
Chương trình rất có thể sẽ gặp sự cố do cuộc gọi tới DestroyMe();
mà có lẽ sẽ phá hủy đối tượng hiện tại đang chạy vòng lặp.
Chúng ta có thể nghĩ nó giống như một bộ phim hoạt hình mà 'kẻ xấu' cưa một cành cây để khiến 'người tốt' gục ngã, nhưng nhận ra quá muộn rằng mình đã đi nhầm đường. Hoặc rắn Midgaard ăn chính đuôi của nó.
Tôi cũng nên nói thêm rằng triệu chứng phổ biến nhất của vòng lặp vô hạn là nó đóng băng chương trình hoặc làm cho nó không đáp ứng. Nó sẽ làm hỏng chương trình nếu nó phân bổ bộ nhớ lặp đi lặp lại, hoặc thực hiện điều gì đó mà kết quả là chia hết cho 0, hoặc tương tự.
Dựa trên nhận xét của Aki Tanaka,
Có thể hữu ích: ảnh chụp màn hình bổ sung của Umiko nói rằng "Nó đã gọi đi gọi lại cùng một hoạt động", điều này có thể không được hiển thị trong mã.
"Nó đã gọi đi gọi lại cùng một hoạt động" Điều này có nhiều khả năng hơn.
Giả sử rằng DestroyMe();
không được thiết kế để được gọi nhiều lần, nó có nhiều khả năng gây ra sự cố.
Một cách để khắc phục sự cố này là thay đổi if
cho một cái gì đó như thế này:
if (m_currentHealth <= 0.f) { m_currentHealth = 0.f; DestroyMe(); break; }
Điều này sẽ thoát khỏi vòng lặp khi DestructibleActor bị phá hủy, đảm bảo rằng 1) DestroyMe
phương thức chỉ được gọi một lần và 2) không áp dụng buff một cách vô ích khi đối tượng đã được coi là đã chết.
- 1 Thoát khỏi vòng lặp for khi sức khỏe <= 0 chắc chắn là một cách khắc phục tốt hơn là đợi đến sau vòng lặp để kiểm tra sức khỏe.
- Tôi nghĩ có lẽ tôi sẽ
break
ra khỏi vòng lặp, và sau đó gọiDestroyMe()
, chỉ để được an toàn
Có một số vấn đề với mã:
- Nếu không có gỡ lỗi, sẽ không có thiệt hại nào được thực hiện.
DestroyMe()
tên chức năng nghe có vẻ nguy hiểm. Tùy thuộc vào cách nó được triển khai, nó có thể là một vấn đề. Nếu đó chỉ là một lệnh gọi tới hàm hủy của đối tượng hiện tại được bao bọc trong một hàm, thì có một vấn đề, vì đối tượng sẽ bị hủy ngay giữa lúc nó đang thực thi mã. Nếu đó là một lệnh gọi đến một hàm xếp hàng đợi sự kiện xóa của đối tượng hiện tại, thì không có vấn đề gì, vì đối tượng sẽ bị hủy sau khi nó hoàn thành việc thực thi và vòng lặp sự kiện bắt đầu.- Vấn đề thực tế dường như được đề cập trong anime, "Nó đã gọi đi lặp lại cùng một hoạt động" - nó sẽ gọi
DestroyMe()
miễn làm_currentHealth <= 0.f
và còn nhiều lỗi hơn để lặp lại, điều này có thể dẫn đếnDestroyMe()
được gọi nhiều lần, lặp đi lặp lại. Vòng lặp sẽ dừng lại sau vòng lặp đầu tiênDestroyMe()
gọi, bởi vì xóa một đối tượng nhiều lần dẫn đến hỏng bộ nhớ, điều này có thể dẫn đến sự cố về lâu dài.
Tôi không thực sự chắc chắn tại sao mỗi lần gỡ lỗi lại lấy đi lượng máu, thay vì lượng máu chỉ bị lấy đi một lần, với hiệu ứng của tất cả các lần gỡ lỗi được áp dụng trên sát thương ban đầu nhận vào, nhưng tôi sẽ cho rằng đó là logic của trò chơi chính xác.
Mã chính xác sẽ là
// 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 - Tôi nên chỉ ra rằng như tôi đã viết các trình cấp phát bộ nhớ trong quá khứ, việc xóa cùng một bộ nhớ không phải là một vấn đề. Nó cũng có thể là thừa. Tất cả phụ thuộc vào hành vi của người cấp phát. Của tôi chỉ hoạt động giống như một danh sách liên kết cấp thấp nên "nút" cho dữ liệu đã xóa hoặc được đặt là miễn phí nhiều lần hoặc bị xóa nhiều lần (sẽ chỉ tương ứng với các chuyển hướng con trỏ dự phòng). Tuy nhiên, bắt tốt.
- Double-free là một lỗi và thường dẫn đến hành vi và sự cố không xác định. Ngay cả khi bạn có một trình cấp phát tùy chỉnh bằng cách nào đó không cho phép sử dụng lại cùng một địa chỉ bộ nhớ, thì không có hai lần là một mã có mùi vì nó không có ý nghĩa gì và bạn sẽ bị các bộ phân tích mã tĩnh la mắng.
- Tất nhiên! Tôi không thiết kế nó cho mục đích đó. Một số ngôn ngữ chỉ yêu cầu trình phân bổ do thiếu tính năng. Không không không. Tôi chỉ nói rằng một vụ tai nạn không được đảm bảo. Một số phân loại thiết kế nhất định không phải lúc nào cũng sụp đổ.