Anonim

AIMBOT 2.0

在新游戏2的第1集中,大约9:40,Nene编写了以下代码:

这是文本形式,带注释的翻译:

// 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循环只是迭代Actor当前具有的debuf。除非Actor具有无限数量的debuf,否则我认为它不可能成为无限循环。

但是我不确定,因为只有一小段代码是他们想在这里放一个复活节彩蛋,对吧?我们本来可以从笔记本电脑的背面拍摄一下,然后听到Umiko说:“哦,那儿有无限循环了。”他们实际上显示了一些代码,这一事实使我认为代码在某种程度上是某种复活节彩蛋。

代码实际上会创建一个无限循环吗?

8
  • 可能有帮助:Umiko的其他屏幕截图说:“ 调用相同的操作 一遍又一遍”,该代码可能不会显示。
  • 哦!我不知道!我看过的@AkiTanaka子说“无限循环”
  • @LoganM我不太同意。不仅仅是OP对某个源于动漫的源代码有疑问; OP的问题是关于特定声明 关于 源代码由动漫中的角色完成,并且有一个与动漫相关的答案,即“ Crunchyroll弄糟并误翻译了行”。
  • @senshin我认为您正在阅读的是您想要的问题而不是实际询问的内容。该问题提供了一些源代码,并询问它是否像现实生活中的C ++代码一样生成无限循环。 新游戏! 是一部虚构的作品;不需要其中的代码来符合现实生活中的标准。 Umiko关于代码的说法比任何C ++标准或编译器都更具权威性。最上面的(可接受的)答案没有提及任何宇宙信息。我认为可以通过一个很好的答案对此问题进行提问,但是不是这样。

代码不是无限循环,而是错误。

有两个(可能是三个)问题:

  • 如果没有气泡,则完全不会造成任何损坏
  • 如果debuf超过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
  • 如果您没有超过16777216 HP,那么4个浮球对健康非常有用。您甚至可以将生命值设置为无限,以创建可以击中但不会死的敌人,并使用无限伤害进行一击攻击,但仍然无法杀死无限HP角色(INF-INF的结果为NaN),但是会杀死其他一切。因此,它非常有用。
  • 1 @cat按照许多编码标准的约定 m_ 前缀意味着它是一个成员变量。在这种情况下,成员变量 DestructibleActor.
  • 2 @HotelCalifornia我同意这是一个很小的机会 ApplyToDamage 不能按预期工作,但是在示例中,我会说 ApplyToDamage 需要重新设计以要求通过原始文件 sourceDamage 以便在这种情况下可以正确计算出气泡。绝对是个狂徒:此时,dmg信息应为包含原始dmg,当前dmg以及损坏性质的结构,如果debufs具有诸如“易燃性”之类的东西。根据经验,任何带有debuf的游戏设计都需要这些。
  • 1 @StephaneHockenhull说得好!

该代码似乎并未创建无限循环。

循环无限循环的唯一方法是

debuf.ApplyToDamage(resolvedDamage); 

或者

DestroyMe(); 

将新项目添加到 m_debufs 容器。

这似乎不太可能。如果是这种情况,由于在迭代时更改容器,程序可能会崩溃。

该程序很可能会由于调用而崩溃 DestroyMe(); 大概会破坏当前正在运行循环的当前对象。

我们可以将其视为动画片,其中“坏人”看到一个分支使“好人”跌倒,但为时已晚,他发现自己错了。或是Midgaard Snake吃着自己的尾巴。


我还应该补充一点,无限循环的最常见症状是冻结程序或使其无响应。如果它重复分配内存,或者执行某些操作最终导致被零除等操作,它将使程序崩溃。


根据田中亚希(Aki Tanaka)的评论,

可能有帮助:Umiko的其他屏幕截图说:“它一次又一次地调用同一操作”,但可能不会在代码中显示。

“它一遍又一遍地调用相同的操作” 这更有可能。

假如说 DestroyMe(); 不能被多次调用,更可能导致崩溃。

解决此问题的一种方法是更改 if 对于这样的事情:

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

当DestructibleActor被销毁时,这将退出循环,请确保1) DestroyMe 方法仅被调用一次,并且2)一旦对象已被视为死亡,就不要无用地施加增益。

2
  • 1当health <= 0时退出for循环绝对是比等到循环之后检查运行状况更好的解决方案。
  • 我想我可能会 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是一个错误,通常会导致未定义的行为和崩溃。即使您有一个自定义的分配器,以某种方式不允许重复使用相同的内存地址,double-free也是一个令人讨厌的代码,因为它毫无意义,并且会被静态代码分析器大喊大叫。
  • 当然!我没有为此设计它。由于缺乏功能,某些语言仅需要分配器。不不不。我只是说不能保证发生崩溃。某些设计分类并不总是会崩溃。