代码之家  ›  专栏  ›  技术社区  ›  Quinn Taylor

处理和报告Objective-C中整数溢出导致的内存分配错误的最佳方法?

  •  9
  • Quinn Taylor  · 技术社区  · 14 年前

    首先,我要说的是,我理解我所描述的问题是如何以及为什么会发生的。我是计算机科学专业的学生,我理解溢出/下溢和有符号/无符号算术(对于那些不熟悉这个主题的人,苹果的安全编码指南 discusses integer overflow

    我的问题是,一旦检测到错误,就报告并从中恢复,更具体地说,在Objective-C框架的情况下(我写并维护 CHDataStructures

    至少 出现这种情况的两种常见情况:

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

    length * sizeof(void*) 字节,我可以检查 length <= UINT_MAX / sizeof(void*) ,因为此测试失败将意味着产品将溢出,并可能分配比预期小得多的内存区域。在支持它的平台上 checkint.h API 更难的是决定如何优雅地处理它。在第一个场景中,调用方可能更好地准备(或至少在思维方式上)处理故障。第二种情况可能发生在代码中向集合添加对象的任何地方,这可能是非常不确定的。

    在这种情况下,当出现整数溢出时,“好公民”Objective-C代码将如何工作? (理想情况下,由于我的项目是一个框架,与COCOA中的基金会基本相同,所以我想对它的最大阻抗匹配模式进行建模。我发现苹果的文档中根本没有提到这一点。)我认为在任何情况下,报告错误都是必然的。由于用于添加对象(可能导致场景2)的API不接受错误参数,如果有什么问题的话,我真的可以做些什么来帮助解决问题?在这种情况下,什么才是真正可以接受的?如果我能做得更好的话,我不愿意故意编写容易崩溃的代码。。。

    5 回复  |  直到 14 年前
        1
  •  3
  •   bbum    14 年前

    目前有两个问题:

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

    在第(1)种情况下,你是被水淹没的(除非失败的分配是愚蠢的大型和大型的);你 知道 失败的分配只是其中一个)。如果发生这种情况,你能做的最好的事情就是尽快崩溃,并留下尽可能多的证据。特别是,创建一个调用 abort() IAmCrashingOnPurposeBecauseYourMemoryIsDepleted()

    如果它真的是(2),那么还有其他问题。具体来说,您能否从这种情况中恢复过来,并且不管用户的数据是否仍然完好无损?如果你能恢复,那么格兰德。。。这样做,用户永远不必知道。如果没有,那么你 . 如果不是,那就救人,死吧。如果用户的数据已损坏,请尽力 不保留损坏的数据 并让用户知道有些地方出了严重的问题。如果用户的数据已持久化,但已损坏,则。。。好。。。哎哟您可能需要考虑创建某种类型的恢复工具。

        2
  •  4
  •   Steve Weller    14 年前

    记录并引发异常。

    对于其他程序员,而不是最终用户,您只能真正成为一个好公民,因此,将问题传递到楼上,并以一种清楚地解释发生了什么、问题是什么(给出数字)以及问题发生在哪里的方式来做,以便消除根本原因。

        3
  •  3
  •   alesplin    14 年前

    关于动态增长、基于阵列的存储,我们只能做这么多。我是超级计算机Moab调度程序的开发人员,我们还处理具有数千个处理器、数千个作业和大量作业输出的系统上的大量数据。在某些情况下,如果不创建一个全新的数据类型来处理大于UINT_MAX或LONG_LONG_MAX等的大小,就不能将缓冲区声明为更大,在大多数“普通”机器上,此时堆栈/堆空间无论如何都会耗尽。因此,我建议记录一条有意义的错误消息,防止集合爆炸,如果用户需要向CHDataStructures集合添加那么多内容,他们应该知道存在处理非常大的数字的问题,调用方应该检查添加是否成功(跟踪集合的大小,等等)。

    另一种可能是,当无法分配具有无符号int或无符号long的较大数组时,将基于数组的存储转换为动态分配的、基于链表的存储。这将是昂贵的,但很少发生,因此对框架的用户来说不会太引人注目。由于动态分配的、基于链表的集合的大小限制是堆的大小,因此任何向集合添加足够多的项以“溢出”集合的用户都会遇到比其项是否成功添加更大的问题。

        4
  •  1
  •   Dave DeLong    14 年前

    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_访问而消亡(我编译并运行了一个32位的应用程序,所以当我点击2**32对象时,我可以确保空间不足。

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

        5
  •  0
  •   Nick Toumpelis    14 年前

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

    Assertions and Logging