由于 Objective-C 中的整数溢出,处理和报告内存分配错误的最佳方法是啥?
Posted
技术标签:
【中文标题】由于 Objective-C 中的整数溢出,处理和报告内存分配错误的最佳方法是啥?【英文标题】:Best way to handle and report memory allocation errors due to integer overflow in Objective-C?由于 Objective-C 中的整数溢出,处理和报告内存分配错误的最佳方法是什么? 【发布时间】:2011-01-16 00:47:37 【问题描述】:首先,让我说我理解我所描述的问题如何以及为什么会发生。我是计算机科学专业的,我了解上溢/下溢和有符号/无符号算术。 (对于不熟悉该主题的人,Apple 的安全编码指南discusses integer overflow 简要介绍。)
我的问题是关于在检测到此类错误后报告并从中恢复,尤其是在 Objective-C 框架的情况下。 (我编写并维护CHDataStructures。)我有一些集合类,它们为存储对象分配内存并根据需要动态扩展。我还没有看到任何与溢出相关的崩溃,可能是因为我的测试用例大多使用健全的数据。但是,鉴于未经验证的值,事情可能会很快爆发,我想防止这种情况发生。
我已经确定了至少两种可能发生这种情况的常见情况:
-
调用者将一个非常大的无符号值(或负符号值)传递给
-initWithCapacity:
。
已添加足够多的对象以导致容量动态扩展,并且容量已增长到足以导致溢出。
简单的部分是检测是否会发生溢出。 (例如,在尝试分配length * sizeof(void*)
字节之前,我可以检查是否length <= UINT_MAX / sizeof(void*)
,因为未通过此测试将意味着产品将溢出并可能分配比预期小得多的内存区域。在支持它的平台上, checkint.h API 是另一种选择。)更难的部分是确定如何优雅地处理它。在第一种情况下,调用者可能更有能力(或至少在心态上)来处理失败。第二种情况可能发生在将对象添加到集合的代码中的任何位置,这可能是非常不确定的。
那么,我的问题是:在这种情况下发生整数溢出时,“好公民”Objective-C 代码应该如何处理?(理想情况下,因为我的项目是一个框架本着与 Cocoa 中的 Foundation 相同的精神,我想对它的行为方式进行建模,以实现最大的“阻抗匹配”。我发现的 Apple 文档根本没有提及这一点。)我认为在无论如何,报告错误是给定的。由于添加对象的 API(这可能导致场景 2)不接受错误参数,如果有的话,我真的可以做些什么来帮助解决问题?在这种情况下,什么才是真正可以接受的?如果我能做得更好,我不愿意故意编写容易崩溃的代码......
【问题讨论】:
【参考方案1】:记录并引发异常。
你只能成为其他程序员的好公民,而不是最终用户,所以将问题传递到楼上,并以清楚地解释发生了什么、问题是什么(给出数字)以及问题在哪里的方式去做正在发生,因此可以消除根本原因。
【讨论】:
这是一个很好的答案,但我只能选择一个,而且@bbum 的答案还有更多有用的细节和背景。【参考方案2】:手头有两个问题:
(1) 分配失败,内存不足。
(2) 您检测到溢出或其他错误情况,如果您继续,将导致 (1)。
在 (1) 的情况下,您会被淹没(除非失败的分配既愚蠢又大,并且您知道失败的分配只是那个)。如果发生这种情况,您能做的最好的事情就是尽快崩溃并留下尽可能多的证据。特别是,创建一个调用名称为 IAmCrashingOnPurposeBecauseYourMemoryIsDepleted()
的 abort()
的函数会在崩溃日志中留下证据。
如果真的是 (2),那么还有其他问题。具体来说,您能否从这种情况中恢复过来,并且无论如何,用户的数据是否仍然完好无损?如果你能恢复,那么盛大......这样做,用户永远不必知道。如果没有,那么您需要绝对确保用户的数据没有损坏。如果不是,则保存并死亡。如果用户的数据已损坏,请尽量不要保留损坏的数据,并让用户知道出现了严重错误。如果用户的数据已经持久化,但已损坏,那么……嗯……哎呀……您可能需要考虑创建某种恢复工具。
【讨论】:
很高兴,因为有问题的代码是一个低级集合框架,我真的不能特别担心任何用户数据,因为我不知道如何保存它或检查是否损坏。在 (2) 可能发生的少数几个地方,我将计划做一些智能的事情,比如尝试更小的分配(如果它是一个正在增长的集合)或返回 nil。无论如何,我一定会记录证据以帮助追查问题。感谢您提供有用的上下文。【参考方案3】:关于动态增长的、基于数组的存储,可以做的只有这么多。我是超级计算机 Moab 调度程序的开发人员,我们还处理具有数千个处理器、数千个作业和大量作业输出的系统上的大量数据。在某些时候,您不能将缓冲区声明为更大,除非创建一个全新的数据类型来处理大于 UINT_MAX 或 LONG_LONG_MAX 等的大小,此时在大多数“正常”机器上,您将无论如何,堆栈/堆空间都用完了。所以我会说记录一个有意义的错误消息,防止集合爆炸,如果用户需要向 CHDataStructures 集合添加这么多东西,他们应该知道处理非常大的数字时存在问题,并且调用者应该检查添加是否成功(跟踪集合的大小等)。
当您无法使用 unsigned int 或 unsigned long 分配更大的数组时,另一种可能性是将基于数组的存储转换为动态分配的、基于链表的存储。这会很昂贵,但很少发生,以至于框架的用户不会特别注意到它。由于动态分配的、基于链表的集合的大小限制是堆的大小,任何向集合添加足够项目以“溢出”的用户都会遇到比他的项目是否存在更大的问题。添加成功。
【讨论】:
【参考方案4】:我想说正确的做法是做 Cocoa 系列所做的事情。例如,如果我有以下代码:
int main (int argc, const char * argv[])
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableArray * a = [[NSMutableArray alloc] init];
for (uint32_t i = 0; i < ULONG_MAX; ++i)
for (uint32_t i = 0; i < 10000000; ++i)
[a addObject:@"foo"];
NSLog(@"%lu rounds of 10,000,000 completed", i+1);
[a release];
[pool drain];
return 0;
..让它运行,它最终会因 EXC_BAD_ACCESS 而死。 (我将其编译为 32 位应用程序并运行,因此当我遇到 2**32 个对象时,我可以确定空间不足。
换句话说,抛出异常会很好,但我认为你真的不需要做任何事情。
【讨论】:
【参考方案5】:使用断言和自定义断言处理程序可能是您的最佳选择。
使用断言,您可以轻松地在代码中设置许多检查点,您可以在其中验证事情是否按应有的方式工作。如果没有,默认情况下,断言宏会记录错误(开发人员定义的字符串),并引发异常。您还可以使用自定义断言处理程序覆盖默认行为,并实现一种不同的方式来处理错误情况(甚至避免抛出异常)。
这种方法具有更大的灵活性,您可以随时轻松修改错误处理策略(抛出异常与内部处理错误)。
文档非常简洁:Assertions and Logging。
【讨论】:
以上是关于由于 Objective-C 中的整数溢出,处理和报告内存分配错误的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
请问Objective-c 内存溢出问题经验汇总,那个好心人共享一下