是否可以在 Linux 上预测 C 语言的堆栈溢出?
题
在 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);
}
解决方案
您可以通过查找进程堆栈空间的大小然后减去已使用的量来确定进程可用的堆栈空间。
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;
}
其他提示
弃用的Alloca()例程(例如malloc(),但使用堆栈,自动释放自身,如果Sigsegv太大,也会用sigsegv吹来。
为什么 alloca 被弃用?
无论如何,在你的情况下,alloca 与 malloc 相比要快多少?(这值得么?)
如果没有足够的空间,你不会从 alloca 返回 null 吗?(和malloc一样吗?)
当你的代码崩溃时,它在哪里崩溃?是在 alloca 中还是在 doStuff() 中?
/约翰
不确定这是否适用于 Linux,但在 Windows 上,大堆栈分配可能会遇到访问冲突 即使他们成功了!
这是因为默认情况下,Windows 的 VMM 实际上仅将堆栈 RAM 的前几个(不确定到底有多少个)4096 字节页面标记为可分页(即,可分页)。由页面文件支持),因为它认为堆栈访问通常会从顶部向下进行;随着访问越来越接近当前的“边界”,越来越低的页面被标记为可分页。但这意味着远低于堆栈顶部的早期内存读/写将触发访问冲突,因为该内存实际上尚未分配!
alloca() 将在失败时返回 NULL,我相信 alloca(0) 的行为是未定义的并且是平台变体。如果你在 do_something() 之前检查这一点,你就永远不会被 SEGV 击中。
我有一些问题:
- 为什么,哦为什么,你需要在堆栈上放那么大的东西?大多数系统的默认大小是 8M,这还是太小了吗?
- 如果调用 alloca() 的函数阻塞,将通过 mlock() / mlockall() 保护相同数量的堆,保证接近相同的访问性能(即“不要交换我,兄弟!”)随着时间的推移?如果您使用更积极的“rt”调度程序,建议无论如何调用它们。
这个问题很有趣,但引起了人们的注意。它抬起了我的方钉圆孔仪表上的指针。
您没有过多说明为什么要在堆栈上分配,但如果堆栈内存模型有吸引力,您也可以在堆上实现堆栈分配。在程序开始时分配一大块内存,并保留指向该内存的堆栈,该堆栈与常规堆栈上的帧相对应。您只需要记住在函数返回时弹出您的私有堆栈指针即可。
几个编译器,例如 打开 Watcom C/C++, ,支持 stackavail() 函数,让您可以做到这一点
您可以使用 GNU libsigsegv
到 处理 页面错误,包括发生堆栈溢出的情况(来自其网站):
在某些应用程序中,堆栈溢出处理程序会执行一些清理操作或通知用户,然后立即终止应用程序。在其他应用程序中,堆栈溢出处理程序 longjmp 返回到应用程序中的中心点。该库支持这两种用途。在第二种情况下,处理程序必须确保恢复正常的信号掩码(因为在执行处理程序时许多信号被阻塞),并且还必须调用 sigsegv_leave_handler() 来转移控制权;那么只有它可以longjmp走。
分配函数是 不是 已弃用。然而,它不在 POSIX 中,并且它也依赖于机器和编译器。Linux 的 alloca 手册页指出,“对于某些应用程序,与使用 malloc 相比,使用它可以提高效率,并且在某些情况下,它还可以简化使用 longjmp() 或 siglongjmp() 的应用程序中的内存释放。否则,不鼓励使用它。”
联机帮助页还指出“如果堆栈帧无法扩展,则不会出现错误指示。然而,在分配失败后,程序很可能会收到 SIGSEGV。”
malloc的性能其实上也提到过 Stackoverflow 播客 #36.
(我知道这不是您问题的正确答案,但我认为无论如何它可能有用。)
我想不出什么好办法。也许可以通过使用 getrlimit() (之前建议)和一些指针算术?但首先要问自己是否真的想要这个。
void *closeToBase; main () { int closeToBase; stackTop = &closeToBase; } int stackHasRoomFor(int bytes) { int currentTop; return getrlimit(...) - (¤tTop - closeToBase) > bytes + SomeExtra; }
就我个人而言,我不会这样做。在堆上分配大的东西,堆栈不适合它。
堆栈区域的末尾由操作系统动态确定。尽管您可以通过以高度依赖操作系统的方式查看虚拟内存区域 (VMA) 来找到堆栈的“静态”边界(请参阅中的 stackvma* 文件) libsigsegv/src/),你还必须考虑
- getrlimit 值,
- 每个线程堆栈大小(参见 pthread_getstacksize)
如果这是显而易见的情况,我深表歉意,但您可以轻松地编写一个函数来测试特定的堆栈分配大小,只需尝试分配(该大小)并捕获堆栈溢出异常。如果您愿意,可以将其放入函数中,并使用一些预先确定的数学函数来计算函数堆栈开销。例如:
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;
}