C ++游戏如何处理内存分配失败?


23

我知道有几种用C ++编写的游戏,但是没有使用异常。由于C ++中的内存分配失败处理通常围绕std::bad_alloc异常进行,因此这些游戏如何处理此类失败?

它们只是崩溃了,还是有另一种方法来处理内存不足的错误并从中恢复?


1
请注意,在某些环境/操作系统中,分配声称成功,但是尝试使用内存会使您的(或其他)程序崩溃
PlasmaHH

6
如果游戏过程中分配失败,《雷神之锤3》将带错误消息返回菜单。我相信Quake 3的池分配器使这成为可能,如果池用完了,它可以删除整个池并安全地返回菜单。
Dietrich Epp

16
通常,会崩溃。
user253751 '16

1
如果确实禁用了异常,则该程序必须改为调用std::terminate,仅此而已。但是,在相同的限制下,分配可以返回空指针而不是引发异常,并且可以分别检查和处理结果。
underscore_d

2
在C ++中,您可以说ClassName variableName = new(nothrow) ClassName();显然是在替换类名,变量名等)。如果分配失败,您可以通过说if(!variableName)允许在没有try-catch异常块的情况下处理错误来检测到它。同样地,如果存储器被使用函数等分配malloc()calloc()等等,那么故障来分配可使用相同的被检测if(!variableName)方法,而不需要一个try-catch。至于游戏如何处理这些错误,那时候,由游戏开发者决定是否崩溃。
Spencer D

Answers:


63

与所有普通课程相同:并非如此。*

对于大多数应用程序,没有客户期望它们一旦内存用完就可以继续工作。所有游戏都属于这些“大多数应用程序”。花时间和金钱在客户不希望工作的边缘情况下工作是没有意义的。

问题类似于以下内容:

  • 如果硬盘已满,如何安装游戏?
  • 您仍如何在低于最低规格的PC上以高fps的速度运行游戏?

答案是相同的:这些是​​用户的问题。游戏不在乎。


*实际上,他们通常会高级别捕获异常,并使用在游戏开始时预先分配的内存,以便尝试在崩溃/终止之前记录事件。然后,该日志可使客户支持人员在此问题上浪费更少的时间。


2
在内存分配失败的情况下,在为日志等预先分配内存时:stackoverflow.com/questions/7749066/…–
bwDraco

23

通常,这种情况永远不会发生。

首先,现代操作系统上的虚拟内存意味着无论如何在正常操作中都不太可能发生虚拟内存;除非您有失控的分配错误,否则游戏将从OS的虚拟地址空间分配内存,并且OS将在调入和调出页面后进行查找。

一切都很好,但是它并不真正适用于控制台或较旧的操作系统,因此,第二,游戏甚至都不会使用标准库分配器进行大量小的动态分配。相反,他们要做的是在启动时一次性分配一个大的内存池,然后从该池中提取所需的任何运行时分配(例如,通过使用C ++放置新的位置或通过在其上编写自己的内存管理系统顶部)。

这也可以防止内存泄漏,因为游戏不必跟踪和说明每个小小的单独分配,而是可以丢弃整个池来回收内存。

第三,游戏将始终设置最低规格并在此范围内预算其内存使用量。因此,如果指定游戏需要1gb的RAM,则意味着只要其内存使用量永远不会超过1gb,就无需担心RAM不足。


3
“相反,他们将要做的是在启动时一次性分配一个大的内存池,然后从该池中提取所需的任何运行时分配”-如果池已满,您会怎么办?
user253751 '16

@immibis-请参阅我的第三点。
Maximus Minimus

2
@immibis你的意思是...如果游泳池是空的,他们怎么办?与系统内存不同,池的大小和使用完全在应用程序的控制之下。因此,除非应用程序存在错误,否则池不能为空。
大卫·史瓦兹

@DavidSchwartz或者,除非内核正在使用内存过量使用。Linux为此。如果通过zswap或zram启用了页面文件压缩,则即使将池清零也无济于事。
Damian Yerrick '16

1
这似乎并不能回答实际问题,而是说类似于“这艘船是不沉的,所以我们需要什么紧急程序?”
Lilienthal

2

好吧,主要是,与我们在出现异常之前所做的相同方式一样-旧的“检查返回值方法”。如果分配器不使用异常,则分配器通常会null在分配失败时返回。一个写得好的游戏将检查并以安全的任何方式处理情况。有时,这意味着终止游戏(例如std::terminate)或至少恢复到上一个​​已知的安全状态(例如,从池中分配时,即使处于不安全状态,也可以安全地处置整个池)。

即使在这种情况下,许多游戏还是会在此类情况下使用例外。当有人说“避免在游戏代码中使用异常”时,通常的含义是“仅在真正特殊的情况下使用异常,而不是在普通流控制下使用”。捕获内存不足异常的设置很便宜-成本主要是投入,以及处理异常会带来的复杂性。在典型的内存不足情况下,这两个都不很重要-无论如何,您几乎总是想终止。

请注意,大多数游戏都会崩溃。这听起来并不疯狂-您通常将一些内存使用情况作为目标,并确保您的游戏没有达到该限制(例如,通过在RTS游戏中使用单位计数限制,或通过限制FPS部分中的敌人数量)。即使您没有这样做,通常也不会在任何相当现代的系统上用完内存-例如,您要用完虚拟地址空间(在32位应用程序中)或堆栈。操作系统已经假装您拥有所需的内存,这是虚拟内存提供的抽象之一。关键是,仅仅因为您具有256 MiB的物理RAM并不意味着您的应用程序不能使用4 GiB的内存。有些游戏甚至可能不太介意所有数据都是“


1

捕获异常决定何时引发异常以及异常发生是两个不同的事情。

您需要具有复杂的逻辑来释放程序不需要的内存。更糟糕的是,如果任何其他正在运行的程序或库正在泄漏​​内存,那么您将无法控制它

您只能做一件好事,以使其正常关闭,这就是大多数程序都会选择执行的操作。您甚至无法决定等到内存可用后再进行操作,因为这会使您的程序无响应。


如果有问题的游戏如OP所说,从字面上看是“不使用例外”,那么它们就不能捕捉,引发或处理其中的一个。如果禁用了异常并且有人抛出异常,则该程序必须改为调用std::terminate,仅此而已。但是在这种情况下,分配可以返回空指针而不是抛出异常,并且可以单独处理。
underscore_d
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.