捕获堆栈溢出

在C中捕获堆栈溢出的最佳方法是什么?

进一步来说:

AC程序包含脚本语言的解释器。

脚本不受信任,可能包含无限的递归错误。 口译员必须能够抓住这些并顺利地继续下去。 (显然,这可以部分地通过使用软件堆栈来处理,但是如果可以用C语言编写大量的库代码块,性能会大大提高;至少,这需要在脚本创建的递归数据结构上运行C函数。)

捕获堆栈溢出的首选forms将涉及longjmp返回主循环。 (丢弃在主循环下面的堆栈帧中保存的所有数据是完全可以的。)

回退可移植解决方案是使用局部变量的地址来监视当前堆栈深度,并使用每个递归函数来包含对使用此方法的堆栈检查函数的调用。 当然,这会在正常情况下产生一些运行时开销; 它也意味着如果我忘记将堆栈检查调用放在一个地方,解释器将有一个潜在的错误。

有没有更好的方法呢? 具体来说,我不期待更好的便携式解决方案,但如果我有一个针对Linux的系统特定解决方案和另一个针对Windows的解决方案,那就没关系。

我已经看到在Windows上引用了一些称为结构化exception处理的东西,尽管我所看到的引用一直是将它转换为C ++exception处理机制; 可以从C访问它,如果是这样,它对这种情况有用吗?

我了解Linux可以捕获分段故障信号; 是否可以将其可靠地转换为longjmp回到主循环?

Java似乎支持在所有平台上捕获堆栈溢出exception; 它是如何实现的?

在我的脑海中,捕获过多堆栈增长的一种方法是检查堆栈帧地址的相对差异:

 #define MAX_ROOM (64*1024*1024UL) // 64 MB static char * first_stack = NULL; void foo(...args...) { char stack; // Compare addresses of stack frames if (first_stack == NULL) first_stack = &stack; if (first_stack > &stack && first_stack - &stack > MAX_ROOM || &stack > first_stack && &stack - first_stack > MAX_ROOM) printf("Stack is larger than %lu\n", (unsigned long)MAX_ROOM); ...code that recursively calls foo()... } 

这将foo()的第一个堆栈帧的地址与当前堆栈帧地址进行比较,如果差值超过MAX_ROOM则会写入消息。

当然,这假设您使用的是一种使用线性始终向下扩展或始终在成长的堆栈的体系结构。

您无需在每个函数中执行此检查,但通常会在达到您选择的限制之前捕获过大的堆栈增长。

AFAIK,所有检测堆栈溢出的机制都会产生一些运行时成本。 您可以让CPU检测到seg-faults,但这已经太晚了; 你可能已经在一些重要的东西上乱涂乱画了。

您说您希望您的解释器尽可能多地调用预编译的库代码。 这很好,但是为了维护沙盒的概念,你的解释器引擎应该总是负责例如堆栈转换和内存分配(从解释语言的角度来看); 您的库例程应该可以实现为回调。 原因是你需要在一个点上处理这类事情,原因是你已经指出过(潜在的错误)。

Java之类的东西通过生成机器代码来解决这个问题,因此它只是生成代码以在每次堆栈转换时检查这一点的情况。

(我不会因为“更好”的解决方案而依赖于特定的平台来打扰这些方法。他们通过限制语言设计和可用性来制造麻烦 ,收益甚微。对于Linux和Windows上的“正常工作”,请参见上文。)

首先, 从C的意义上说,你不能以便携的方式做到这一点 。 实际上,ISO C根本不要求“堆栈”。 讽刺的是,甚至在自动对象的分配失败时,根据第4p2条,行为实际上是未定义的 – 根本无法保证当嵌套过深时调用会发生什么。 你必须依赖一些额外的实现假设( ISAOS ABI )才能做到这一点,所以你最终得到C +别的东西,而不仅仅是C.运行时机器代码生成也不能在C级中移植。

(顺便说一句,ISO C ++有一个堆栈展开的概念,但仅在exception处理的上下文中。并且仍然无法保证堆栈溢出的可移植行为;虽然它似乎未指定,但未定义。)

除了限制调用深度之外,所有方式都有一些额外的运行时成本。 除非有一些硬件辅助手段将其摊销(如页表行走),否则成本将很容易被观察到。 可悲的是,现在情况并非如此。

我找到的唯一可移植方式是不依赖底层机器架构的本机堆栈。 这通常意味着您必须将激活记录帧分配为免费存储(在堆上)的一部分,而不是ISA提供的本机堆栈。 这不仅适用于解释型语言实现,也适用于已编译的语言实现,例如SML / NJ。 这种软件堆栈方法并不总是会产生更差的性能,因为它们允许在对象语言中提供更高级别的抽象,因此程序可能有更多的机会进行优化,尽管它不太可能在天真的解释器中。

您有几种方法可以实现这一目标。 一种方法是编写虚拟机 。 您可以分配内存并在其中构建堆栈。

另一种方法是在您的实现中编写复杂的异步样式代码(例如, trampolinesCPS转换 ),尽可能依赖于较少的本机调用帧 。 通常很难做到正确,但它确实有效。 通过这种方式启用的其他function是更容易的尾部调用优化和更容易的一流继续捕获。