4

在 x86 Linux 系统上,某些情况可能会导致堆栈溢出:

  • struct my_big_object[HUGE_NUMBER]在堆栈上。穿过它最终会导致SIGSEGV
  • alloca()例程(如malloc(),但使用堆栈,自动释放自身,SIGSEGV如果它太大也会爆炸)。更新: alloca() 并没有像我最初所说的那样被正式弃用;它只是气馁

有没有办法以编程方式检测本地堆栈对于给定对象是否足够大?我知道堆栈大小可以通过 调整ulimit,所以我希望有一种方法(但它可能是不可移植的)。理想情况下,我希望能够做这样的事情:

int min_stack_space_available = /* ??? */;
if (object_size < min_stack_space_available)
{
    char *foo = alloca(object_size);
    do_stuff(foo);
}
else
{
    char *foo = malloc(object_size);
    do_stuff(foo);
    free(foo);
}
4

12 回答 12

5

您可以通过查找进程堆栈空间的大小然后减去已使用的量来确定进程可用的堆栈空间。

ulimit -s

显示 linux 系统上的堆栈大小。对于编程方法,请查看getrlimit()。然后,要确定当前堆栈深度,从一到底部减去指向堆栈顶部的指针。例如(未经测试的代码):

unsigned char *bottom_of_stack_ptr;

void call_function(int argc, char *argv) {
    unsigned char top_of_stack;
    unsigned int depth = (&top_of_stack > bottom_of_stack_ptr) ? 
        &top_of_stack-bottom_of_stack_ptr : 
        bottom_of_stack_ptr-&top_of_stack;

    if( depth+100 < PROGRAMMATICALLY_DETERMINED_STACK_SIZE ) {
        ...
    }
}

int main(int argc, char *argv) {
    unsigned char bottom_of_stack;
    bottom_of_stack_ptr = &bottom_of_stack;
    my_function();
    return 0;
}
于 2009-01-09T07:45:36.927 回答
1

已弃用的 alloca() 例程(如 malloc(),但使用堆栈,自动释放自身,如果它太大,也会用 SIGSEGV 炸毁)。

为什么不推荐使用alloca?

无论如何,在您的情况下,alloca 与 malloc 的速度有多快?(这值得么?)

如果没有足够的空间,你不会从 alloca 得到 null 吗?(和 malloc 一样吗?)

当你的代码崩溃时,它在哪里崩溃?它在 alloca 中还是在 doStuff() 中?

/约翰

于 2009-01-09T06:59:23.417 回答
1

alloca() 将在失败时返回 NULL,我相信 alloca(0) 的行为是未定义的和平台变体。如果你在 do_something() 之前检查,你永远不会被 SEGV 击中。

我有一些问题:

  1. 为什么,哦,为什么,你需要堆栈上那么大的东西吗?大多数系统的默认大小是8M,还是太小了?
  2. 如果调用 alloca() 的函数阻塞,通过 mlock() / mlockall() 保护相同数量的堆是否会随着时间的推移保证接近相同的访问性能(即“不要交换我,兄弟!”)?如果您使用更激进的 'rt' 调度程序,建议无论如何调用它们。

这个问题很有趣,但引起了人们的注意。它提高了我方钉圆孔 o-meter 上的指针。

于 2009-01-09T07:25:28.713 回答
1

推荐使用alloca 函数。但是,它不在 POSIX 中,它也依赖于机器和编译器。alloca 的 Linux 手册页指出“对于某些应用程序,与使用 malloc 相比,使用它可以提高效率,并且在某些情况下,它还可以简化使用 longjmp() 或 siglongjmp() 的应用程序中的内存释放。否则,不鼓励使用它。”

手册页还说“如果堆栈帧无法扩展,则没有错误指示。但是,在分配失败后,程序很可能会收到 SIGSEGV。”

在Stackoverflow Podcast #36中实际上提到了 malloc 的性能。

(我知道这不是您问题的正确答案,但我认为无论如何它可能有用。)

于 2009-01-09T08:09:41.200 回答
1

不确定这是否适用于 Linux,但在 Windows 上,即使成功,也可能会遇到使用大堆栈分配的访问冲突!

这是因为默认情况下,Windows 的 VMM 实际上仅将堆栈 RAM 的前几个(不确定到底有多少)4096 字节页面标记为可分页(即由页面文件支持),因为它认为堆栈访问通常会从顶端; 随着访问越来越接近当前的“边界”,越来越低的页面被标记为可分页。但这意味着远低于堆栈顶部的早期内存读/写将触发访问冲突,因为该内存尚未实际分配!

于 2009-01-09T08:50:43.097 回答
1

您没有过多说明为什么要在堆栈上分配,但如果堆栈内存模型很有吸引力,您也可以在堆上实现堆栈分配。在程序开始时分配一大块内存并保留一个指向它的指针堆栈,这将对应于常规堆栈上的帧。您只需要记住在函数返回时弹出您的私有堆栈指针。

于 2009-01-09T09:16:55.807 回答
1

一些编译器,例如Open Watcom C/C++,支持 stackavail() 函数,可以让你做到这一点

于 2009-01-09T15:14:37.947 回答
1

您可以使用它GNU libsigsegv处理页面错误,包括发生堆栈溢出的情况(来自其网站):

在某些应用程序中,堆栈溢出处理程序执行一些清理或通知用户,然后立即终止应用程序。在其他应用程序中,堆栈溢出处理程序 longjmps 回到应用程序的中心点。这个库支持这两种用途。第二种情况,handler必须保证恢复正常的信号掩码(因为handler执行的时候很多信号被阻塞了),还必须调用sigsegv_leave_handler()来转移控制;那么只有它可以longjmp离开。

于 2009-01-10T14:27:59.803 回答
0

即使这不是对您问题的直接回答,我希望您知道valgrind的存在- 一个在 Linux 上在运行时检测此类问题的绝佳工具。

关于堆栈问题,您可以尝试从检测到这些溢出的固定池中动态分配对象。通过一个简单的宏向导,您可以使其在调试时运行,在发布时运行真实代码,从而知道(至少对于您正在执行的场景)您不会占用太多。这是更多信息和示例实现的链接。

于 2009-01-09T07:49:39.367 回答
0

我想不出一个好的方法。也许可以通过使用 getrlimit() (之前建议)和一些指针算术?但首先问问自己你是否真的想要这个。

无效 *closeToBase;

主要的 () {
  诠释 closeToBase;
  stackTop = &closeToBase;
}

int stackHasRoomFor(int bytes) {
  诠释当前顶部;
  return getrlimit(...) - (¤tTop - closeToBase) > bytes + SomeExtra;
}

就个人而言,我不会这样做。在堆上分配大的东西,堆栈不适合它。

于 2009-01-09T09:05:47.700 回答
0

堆栈区域的结束由操作系统动态确定。虽然您可以通过以高度依赖操作系统的方式查看虚拟内存区域 (VMA) 来找到堆栈的“静态”边界(请参阅libsigsegv/src/中的 stackvma* 文件),但您还必须考虑

于 2016-10-14T19:35:43.777 回答
-2

抱歉,如果这是显而易见的,但您可以轻松地编写一个函数来测试特定的堆栈分配大小,只需尝试(该大小的)alloca 并捕获堆栈溢出异常。如果您愿意,可以将它放入一个函数中,并为函数堆栈开销使用一些预先确定的数学。例如:

bool CanFitOnStack( size_t num_bytes )
{
    int stack_offset_for_function = 4; // <- Determine this
    try
    {
        alloca( num_bytes - stack_offset_for_function );
    }
    catch ( ... )
    {
        return false;
    }

    return true;
}
于 2009-01-09T07:02:47.777 回答