首先,让我说我理解我所描述的问题是如何以及为什么会发生的。我是计算机科学专业,我理解溢出/下溢和签名/无符号算术。 (对于那些不熟悉该主题的人,请简要介绍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)不接受错误参数,我可以做些什么来帮助解决问题,如果有的话?在这种情况下真正考虑的是什么?如果我能做得更好,我不愿意故意编写容易崩溃的代码......
答案 0 :(得分:4)
记录并引发异常。
你真的可以成为其他程序员的好公民,而不是最终用户,所以把问题传递到楼上并以明确解释发生了什么,问题是什么(给出数字)以及它在哪里进行正在发生,因此可以消除根本原因。
答案 1 :(得分:3)
关于动态增长的基于阵列的存储,只能做很多事情。我是Moab超级计算机调度程序的开发人员,我们还在拥有数千个处理器,数千个作业和大量作业输出的系统上处理大量数据。在某些时候,你不能声明缓冲区更大,而不是创建一个全新的数据类型来处理大于UINT_MAX或LONG_LONG_MAX等的大小,此时大多数“普通”机器你将是无论如何都耗尽了堆栈/堆空间。所以我要说一个有意义的错误消息,保持集合不爆炸,如果用户需要将很多东西添加到CHDataStructures集合中,他们应该知道处理非常大的数字和调用者的问题应该检查添加是否成功(跟踪集合的大小等)。
另一种可能性是,当您无法使用unsigned int或unsigned long分配更大的数组时,将基于数组的存储转换为动态分配的基于链表的存储。这将是昂贵的,但很少发生,它不应该是框架的用户非常明显。由于动态分配的,基于链表的集合的大小限制是堆的大小,任何向集合中添加足够的项以“溢出”它的用户都会遇到比他的项目是否是更大的问题。成功添加。
答案 2 :(得分:3)
手头有两个问题:
(1)分配失败,你内存不足。
(2)您检测到溢出或其他错误情况,如果继续,将导致(1)。
在(1)的情况下,你被软管(除非失败的分配都是愚蠢的大和你知道失败的分配只是那个)。如果发生这种情况,您可以做的最好的事情就是尽快崩溃并留下尽可能多的证据。特别是,创建一个调用abort()
之类名称IAmCrashingOnPurposeBecauseYourMemoryIsDepleted()
的函数会在崩溃日志中留下证据。
如果真的是(2),那么还有其他问题。具体来说,您是否可以从情况中恢复,无论用户的数据是否仍然完好无损?如果你可以恢复,那么盛大...这样做,用户永远不必知道。如果没有,那么 需要确保用户的数据没有损坏 。如果不是,那就保存并死掉。如果用户的数据已损坏,请尽力不要保留已损坏的数据并让用户知道某些内容已经出现严重错误。如果用户的数据已经存在但已损坏,那么......好吧......哎哟......你可能要考虑创建某种恢复工具。
答案 3 :(得分:1)
我认为正确的做法是做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个对象时,我可以肯定用完空间。
换句话说,抛出异常会很好,但我认为你真的不需要做任何事情。
答案 4 :(得分:0)
使用断言和自定义断言处理程序可能是最适合您的选项。
使用断言,您可以轻松地在代码中包含许多检查点,您可以在其中验证事情是否正常工作。如果不这样做,默认情况下,断言宏会记录错误(开发人员定义的字符串),并抛出异常。您还可以使用自定义断言处理程序覆盖默认行为,并实现一种不同的方法来处理错误条件(甚至避免抛出异常)。
这种方法可以提供更大程度的灵活性,您可以随时轻松修改错误处理策略(抛出异常与内部处理错误)。
文档非常简洁:Assertions and Logging。