预先在运行时检测堆栈溢出
Detecting stack overflows during runtime beforehand
我有一个相当大的递归函数(另外,我是用C写的),虽然我毫不怀疑发生堆栈溢出的情况是极不可能的,但它仍然是可能的。我想知道的是,你是否可以在几次迭代后检测到堆栈是否会溢出,这样你就可以在不使程序崩溃的情况下进行紧急停止。
在 C 编程语言本身中,这是不可能的。通常,您无法轻易知道自己 运行 在 运行 出栈之前就出栈了。我建议您改为在您的实现中对递归深度设置一个可配置的硬限制,这样您就可以在超过深度时简单地中止。您还可以重写您的算法以使用辅助数据结构而不是通过递归使用堆栈,这使您可以更灵活地检测内存不足的情况; malloc()
失败时告诉您。
但是,您可以在类 UNIX 系统上使用这样的过程获得类似的结果:
- 使用
setrlimit
设置一个低于硬堆栈限制的软堆栈限制
- 为
SIGSEGV
和 SIGBUS
建立信号处理程序以获得堆栈溢出的通知。一些操作系统为这些生成 SIGSEGV
,其他操作系统生成 SIGBUS
.
- 如果您收到这样的信号并确定它来自堆栈溢出,请使用
setrlimit
提高软堆栈限制并设置一个全局变量来识别这种情况的发生。使变量 volatile
这样优化器就不会破坏你的平原。
- 在您的代码中,在每个递归步骤中,检查是否设置了此变量。如果是,则中止。
这可能不适用于所有地方,并且需要特定于平台的代码来找出信号来自堆栈溢出。并非所有系统(特别是早期的 68000 系统)在获得 SIGSEGV
或 SIGBUS
.
后都可以继续正常处理
Bourne shell 使用了类似的方法进行内存分配。
这是一个简单的方法,但是有点恶心...
当您第一次进入该函数时,您可以存储在该函数中声明的其中一个变量的地址。将该值存储在您的函数之外(例如,在全局中)。在随后的调用中,将该变量的当前地址与缓存的副本进行比较。递归得越深,这两个值就越远。
这很可能会导致编译器警告(存储临时变量的地址),但它的好处是让您可以相当准确地知道您正在使用多少堆栈。
不能说我真的推荐这个,但它会起作用。
#include <stdio.h>
char* start = NULL;
void recurse()
{
char marker = '@';
if(start == NULL)
start = ▮
printf("depth: %d\n", abs(&marker - start));
if(abs(&marker - start) < 1000)
recurse();
else
start = NULL;
}
int main()
{
recurse();
return 0;
}
另一种方法是在程序开始时了解堆栈限制,并在每次递归函数中检查是否已接近此限制(在一些安全范围内,比如 64 kb)。如果是,则中止;如果没有,继续。
POSIX 系统的堆栈限制可以通过使用 getrlimit
系统调用获知。
线程安全的示例代码:(注意:它的代码假设堆栈向后增长,如x86
!)
#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>
void *stack_limit;
#define SAFETY_MARGIN (64 * 1024) // 64 kb
void recurse(int level)
{
void *stack_top = &stack_top;
if (stack_top <= stack_limit) {
printf("stack limit reached at recursion level %d\n", level);
return;
}
recurse(level + 1);
}
int get_max_stack_size(void)
{
struct rlimit rl;
int ret = getrlimit(RLIMIT_STACK, &rl);
if (ret != 0) {
return 1024 * 1024 * 8; // 8 MB is the default on many platforms
}
printf("max stack size: %d\n", (int)rl.rlim_cur);
return rl.rlim_cur;
}
int main (int argc, char *argv[])
{
int x;
stack_limit = (char *)&x - get_max_stack_size() + SAFETY_MARGIN;
recurse(0);
return 0;
}
输出:
max stack size: 8388608
stack limit reached at recursion level 174549
这是一个适用于 win-32 的简单解决方案。实际上类似于 Wossname 已经发布的内容,但不那么恶心 :)
unsigned int get_stack_address( void )
{
unsigned int r = 0;
__asm mov dword ptr [r], esp;
return r;
}
void rec( int x, const unsigned int begin_address )
{
// here just put 100 000 bytes of memory
if ( begin_address - get_stack_address() > 100000 )
{
//std::cout << "Recursion level " << x << " stack too high" << std::endl;
return;
}
rec( x + 1, begin_address );
}
int main( void )
{
int x = 0;
rec(x,get_stack_address());
}
我有一个相当大的递归函数(另外,我是用C写的),虽然我毫不怀疑发生堆栈溢出的情况是极不可能的,但它仍然是可能的。我想知道的是,你是否可以在几次迭代后检测到堆栈是否会溢出,这样你就可以在不使程序崩溃的情况下进行紧急停止。
在 C 编程语言本身中,这是不可能的。通常,您无法轻易知道自己 运行 在 运行 出栈之前就出栈了。我建议您改为在您的实现中对递归深度设置一个可配置的硬限制,这样您就可以在超过深度时简单地中止。您还可以重写您的算法以使用辅助数据结构而不是通过递归使用堆栈,这使您可以更灵活地检测内存不足的情况; malloc()
失败时告诉您。
但是,您可以在类 UNIX 系统上使用这样的过程获得类似的结果:
- 使用
setrlimit
设置一个低于硬堆栈限制的软堆栈限制 - 为
SIGSEGV
和SIGBUS
建立信号处理程序以获得堆栈溢出的通知。一些操作系统为这些生成SIGSEGV
,其他操作系统生成SIGBUS
. - 如果您收到这样的信号并确定它来自堆栈溢出,请使用
setrlimit
提高软堆栈限制并设置一个全局变量来识别这种情况的发生。使变量volatile
这样优化器就不会破坏你的平原。 - 在您的代码中,在每个递归步骤中,检查是否设置了此变量。如果是,则中止。
这可能不适用于所有地方,并且需要特定于平台的代码来找出信号来自堆栈溢出。并非所有系统(特别是早期的 68000 系统)在获得 SIGSEGV
或 SIGBUS
.
Bourne shell 使用了类似的方法进行内存分配。
这是一个简单的方法,但是有点恶心...
当您第一次进入该函数时,您可以存储在该函数中声明的其中一个变量的地址。将该值存储在您的函数之外(例如,在全局中)。在随后的调用中,将该变量的当前地址与缓存的副本进行比较。递归得越深,这两个值就越远。
这很可能会导致编译器警告(存储临时变量的地址),但它的好处是让您可以相当准确地知道您正在使用多少堆栈。
不能说我真的推荐这个,但它会起作用。
#include <stdio.h>
char* start = NULL;
void recurse()
{
char marker = '@';
if(start == NULL)
start = ▮
printf("depth: %d\n", abs(&marker - start));
if(abs(&marker - start) < 1000)
recurse();
else
start = NULL;
}
int main()
{
recurse();
return 0;
}
另一种方法是在程序开始时了解堆栈限制,并在每次递归函数中检查是否已接近此限制(在一些安全范围内,比如 64 kb)。如果是,则中止;如果没有,继续。
POSIX 系统的堆栈限制可以通过使用 getrlimit
系统调用获知。
线程安全的示例代码:(注意:它的代码假设堆栈向后增长,如x86
!)
#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>
void *stack_limit;
#define SAFETY_MARGIN (64 * 1024) // 64 kb
void recurse(int level)
{
void *stack_top = &stack_top;
if (stack_top <= stack_limit) {
printf("stack limit reached at recursion level %d\n", level);
return;
}
recurse(level + 1);
}
int get_max_stack_size(void)
{
struct rlimit rl;
int ret = getrlimit(RLIMIT_STACK, &rl);
if (ret != 0) {
return 1024 * 1024 * 8; // 8 MB is the default on many platforms
}
printf("max stack size: %d\n", (int)rl.rlim_cur);
return rl.rlim_cur;
}
int main (int argc, char *argv[])
{
int x;
stack_limit = (char *)&x - get_max_stack_size() + SAFETY_MARGIN;
recurse(0);
return 0;
}
输出:
max stack size: 8388608
stack limit reached at recursion level 174549
这是一个适用于 win-32 的简单解决方案。实际上类似于 Wossname 已经发布的内容,但不那么恶心 :)
unsigned int get_stack_address( void )
{
unsigned int r = 0;
__asm mov dword ptr [r], esp;
return r;
}
void rec( int x, const unsigned int begin_address )
{
// here just put 100 000 bytes of memory
if ( begin_address - get_stack_address() > 100000 )
{
//std::cout << "Recursion level " << x << " stack too high" << std::endl;
return;
}
rec( x + 1, begin_address );
}
int main( void )
{
int x = 0;
rec(x,get_stack_address());
}