在C中检查可用的堆栈大小

在C中检查可用的堆栈大小

Checking available stack size in C

我正在将MinGW与GCC 3.4.5(mingw-special vista r3)一起使用。

我的C应用程序使用了很多堆栈,所以我想知道是否有什么方法可以通过编程方式告诉您还剩下多少堆栈,以便在发现即将用完时可以完全解决这种情况。

如果不是,您还可以通过其他什么方法来解决可能耗尽堆栈空间的问题?

我不知道我要从多大的堆栈开始,因此也需要以编程方式识别它。


getrusage函数可获取当前用法。 (请参见man getrusage)。

Linux中的getrlimit将有助于使用RLIMIT_STACK参数获取堆栈大小。

1
2
3
4
5
6
7
8
9
10
#include <sys/resource.h>
int main (void)
{
  struct rlimit limit;

  getrlimit (RLIMIT_STACK, &limit);
  printf ("
Stack Limit = %ld and %ld max
"
, limit.rlim_cur, limit.rlim_max);
}

请看一下man getrlimit
相同的信息可以通过ulimit -sulimit -a堆栈大小行获取。
还可以查看setrlimit函数,该函数可以设置限制。
但是,如其他答案中所述,如果您需要调整堆栈,那么您可能应该重新考虑您的设计。 如果要一个大数组,为什么不从堆中取出内存呢?


将局部变量的地址从堆栈中取出将起作用。然后,在一个更嵌套的调用中,您可以减去另一个本地的地址,以找出它们之间的差异

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
size_t top_of_stack;

void Main()
{
  int x=0;
  top_of_stack = (size_t) &x;

  do_something_very_recursive(....)
}

size_t SizeOfStack()
{
  int x=0;
  return top_of_stack - (size_t) &x;
}

如果您的代码是多线程的,则需要处理每个线程的top_of_stack变量。


检查您的编译器是否支持stackavail()


雷蒙德·陈(The Old New Thing)对于此类问题有很好的答案:

If you have to ask, you're probably doing something wrong.

这是有关堆栈分配的Win32详细信息:MSDN。

如果您认为自己可能受到堆栈空间的限制,那么几乎可以肯定会受到可用虚拟内存的限制,在这种情况下,您将需要找到其他解决方案。

您到底想做什么?


对于Windows:在使用来自Kernel32.dll的VirtualQuery函数之前,我已经完成了此操作。 我在C#中只有一个示例,但它演示了该技术:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static class StackManagement
    {
        [StructLayout(LayoutKind.Sequential)]
        struct MEMORY_BASIC_INFORMATION
        {
            public UIntPtr BaseAddress;
            public UIntPtr AllocationBase;
            public uint AllocationProtect;
            public UIntPtr RegionSize;
            public uint State;
            public uint Protect;
            public uint Type;
        };

        private const long STACK_RESERVED_SPACE = 4096 * 16;

        public unsafe static bool CheckForSufficientStack(UInt64 bytes)
        {
            MEMORY_BASIC_INFORMATION stackInfo = new MEMORY_BASIC_INFORMATION();
            UIntPtr currentAddr = new UIntPtr(&stackInfo);
            VirtualQuery(currentAddr, ref stackInfo, sizeof(MEMORY_BASIC_INFORMATION));

            UInt64 stackBytesLeft = currentAddr.ToUInt64() - stackInfo.AllocationBase.ToUInt64();

            return stackBytesLeft > (bytes + STACK_RESERVED_SPACE);
        }

        [DllImport("kernel32.dll")]
        private static extern int VirtualQuery(UIntPtr lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);
    }

顺便说一句:也可以在StackOverflow上的另一个问题上找到此代码,当我尝试修复代码中的错误时,我又问了这个问题:算术运算导致不安全的C#enter链接描述溢出。


这是我已经放弃的问题。通过大量的黑客攻击(主要是祈祷),您可以获得在给定时间在给定计算机上运行的解决方案。但是总的来说,似乎没有做到这一点的体面方法。

您将必须从程序外部获取堆栈的位置和大小(在Linux上,您可以从/proc//maps获取)。在程序中,您必须以某种方式测试堆栈的位置。可以使用局部变量,但是不能真正保证它们确实在堆栈中。您也可以尝试通过某些程序集从堆栈指针寄存器中获取值。

因此,现在您有了堆栈的位置,堆栈的大小和当前位置,并假设您知道堆栈向哪个方向增长。什么时候进入堆栈溢出模式?您最好不要接近尾声,因为您的估算值(即局部变量的地址或来自堆栈指针的值)可能有点过于乐观;在堆栈指针之外寻址内存并不罕见。同样,您不知道任何给定函数(及其调用的函数)在堆栈上需要多少空间。因此,您最后必须留出一些空间。

我只能建议您不要陷入困境,并尽量避免进行深度递归。您可能还想增加堆栈大小;我相信,在Windows上,您必须将其编译为可执行文件。


假设您知道完整堆栈的大小,则可以添加一些汇编代码来读取ESP。
如果您阅读ESP并将其保存在main函数中,则可以将当前的ESP与main中的ESP进行比较,并查看ESP发生了多少变化。这样可以指示您使用了多少堆栈。


在Linux上,您将调用getrusage并检查返回的struct rusage的
ru_isrss成员(集成的未共享堆栈大小)。

从MINGW站点及其sourceforge站点对补丁的跟踪来看,我发现在2008年5月,围绕getrusage进行了一些补丁,并且似乎已经得到了相当长的支持。您应该仔细检查MinGW支持多少典型Linux功能方面的任何警告。


也许这仅对Windows平台有用:

在您的exe文件的PE标头(IMAGE_NT_HEADERS)中,有一些记录,例如:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

typedef struct _IMAGE_OPTIONAL_HEADER {
    ...
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    ...
}

有一种简单的方法来获取这些值:使用GetModuleHandle(NULL)将为您提供模块的图像库(句柄),并在其中找到IMAGE_DOS_HEADER结构的地址,这将帮助您找到IMAGE_NT_HEADERS结构(imagebase + IMAGE_DOS_HEADER。 e_lfanew)-> IMAGE_NT_HEADERS,然后在其中找到这些字段:SizeOfStackReserve和SizeOfStackCommit。

操作系统将为您的堆栈分配的最大空间为SizeOfStackReserve。

如果您考虑尝试此操作,请告诉我,我们将为您提供帮助。有一种方法可以获取特定点使用的堆栈大小。


推荐阅读