9

首先,让我说我理解我所描述的问题如何以及为什么会发生。我是计算机科学专业的,我了解上溢/下溢和有符号/无符号算术。(对于不熟悉该主题的人,Apple 的安全编码指南简要讨论了整数溢出。)

我的问题是关于在检测到此类错误后报告并从中恢复,尤其是在 Objective-C 框架的情况下。(我编写和维护CHDataStructures。)我有一些集合类,它们为存储对象分配内存并根据需要动态扩展。我还没有看到任何与溢出相关的崩溃,可能是因为我的测试用例大多使用健全的数据。但是,考虑到未经验证的值,事情可能会很快爆发,我想防止这种情况发生。

我已经确定了至少两种可能发生这种情况的常见情况:

  1. 调用者将一个非常大的无符号值(或负符号值)传递给-initWithCapacity:.
  2. 添加了足够多的对象导致容量动态扩展,并且容量已经增长到足以导致溢出。

简单的部分是检测是否会发生溢出。(例如,在尝试分配length * sizeof(void*)字节之前,我可以检查是否length <= UINT_MAX / sizeof(void*),因为未通过此测试将意味着产品将溢出并可能分配比预期小得多的内存区域。在支持它的平台上,checkint.h API是另一种选择。)更难的部分是确定如何优雅地处理它。在第一种情况下,调用者可能更有能力(或至少在心态上)来处理失败。第二种情况可能发生在将对象添加到集合的代码中的任何位置,这可能是非常不确定的。

那么,我的问题是:在这种情况下发生整数溢出时,“好公民”Objective-C 代码应该如何行动?(理想情况下,由于我的项目是一个与 Cocoa 中的 Foundation 精神相同的框架,我想模拟它的行为方式以实现最大的“阻抗匹配”。我发现的 Apple 文档在关于这一切。)我认为无论如何,报告错误是给定的。由于添加对象的 API(这可能导致场景 2)不接受错误参数,如果有的话,我真的可以做些什么来帮助解决问题?在这种情况下,什么才是真正可以接受的?如果我能做得更好,我不愿意故意编写容易崩溃的代码......

4

5 回答 5

4

记录并引发异常。

你只能真正成为其他程序员的好公民,而不是最终用户,所以将问题传递到楼上并以清楚地解释正在发生的事情,问题是什么(给出数字)以及发生的地方的方式来做可以消除根本原因。

于 2010-03-07T06:03:05.837 回答
3

关于动态增长、基于阵列的存储,可以做的只有这么多。我是超级计算机 Moab 调度程序的开发人员,我们还处理具有数千个处理器、数千个作业和大量作业输出的系统上的大量数据。在某些时候,您不能将缓冲区声明为更大,除非创建一个全新的数据类型来处理大于 UINT_MAX 或 LONG_LONG_MAX 等的大小,此时在大多数“正常”机器上,您将无论如何,堆栈/堆空间都用完了。所以我会说记录一个有意义的错误消息,防止集合爆炸,如果用户需要向 CHDataStructures 集合添加这么多东西,他们应该知道处理非常大的数字时存在问题,

另一种可能性是将基于数组的存储转换为动态分配的、基于链表的存储,当您无法使用 unsigned int 或 unsigned long 分配更大的数组时。这会很昂贵,但很少发生,以至于框架的用户不会特别注意到它。由于动态分配的、基于链表的集合的大小限制是堆的大小,任何向集合添加足够项目以“溢出”的用户都会遇到比他的项目是否存在更大的问题。添加成功。

于 2010-02-12T06:50:23.383 回答
3

手头有两个问题:

(1) 分配失败,内存不足。

(2) 您检测到溢出或其他错误情况,如果您继续,将导致 (1)。

在 (1) 的情况下,您会被淹没(除非失败的分配既愚蠢又大,并且您知道失败的分配只是那个)。如果发生这种情况,您能做的最好的事情就是尽快崩溃并留下尽可能多的证据。特别是,创建一个调用abort()类似名称的函数IAmCrashingOnPurposeBecauseYourMemoryIsDepleted()会在崩溃日志中留下证据。

如果真的是(2),那么还有其他问题。具体来说,您能否从这种情况中恢复过来,并且无论如何,用户的数据是否仍然完好无损?如果你能恢复,那么盛大......这样做,用户永远不必知道。如果没有,那么您需要绝对确保用户的数据没有损坏。如果不是,则保存并死亡。如果用户的数据已损坏,请尽量不要保留损坏的数据,并让用户知道出现了严重错误。如果用户的数据已经被持久化了,但是已经损坏了,那么……好吧……哎呀……您可能需要考虑创建某种恢复工具。

于 2010-03-11T03:39:23.030 回答
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 个对象时,我可以确保空间不足。

换句话说,抛出异常会很好,但我认为你真的不需要做任何事情。

于 2010-03-07T18:16:19.500 回答
0

使用断言和自定义断言处理程序可能是您的最佳选择。

使用断言,您可以轻松地在代码中设置许多检查点,您可以在其中验证事情是否按应有的方式工作。如果没有,默认情况下,断言宏会记录错误(开发人员定义的字符串),并引发异常。您还可以使用自定义断言处理程序覆盖默认行为,并实现一种不同的方式来处理错误情况(甚至避免抛出异常)。

这种方法允许更大程度的灵活性,并且您可以随时轻松地修改错误处理策略(抛出异常与内部处理错误)。

文档非常简洁:Assertions and Logging

于 2010-03-09T18:09:40.237 回答