如何在分配大堆栈大小后消除堆栈溢出错误?

时间:2017-04-25 13:44:50

标签: c visual-studio-2015

Unhandled exception at 0x00AA9379 in A.exe: 0xC00000FD: Stack overflow (parameters: 0x00000000, 0x00802000).

我正面临堆栈溢出错误。我使用VS15作为我的IDE。我试图为堆栈分配更多内存。为此,我使用了Project >> Properties >> Linker >> System >> Stack allocation并为堆栈分配了4GB。但错误继续在此行的chkstk.asm处停止

99 sub eax, _PAGESIZE_ ; decrease by PAGESIZE

但问题并未解决。我如何提前知道我需要多少堆栈大小?我已经为所有大变量使用了动态内存分配。但无法解决问题。这是一个可验证的例子......

这是我的代码:

#include <stdio.h>

void main(void)
{
    FILE    *fp1;
    char    datfile[132];
    int    nod[1024 * 1024];
    int    Enod[8 * 1024 * 1024];
    double    nodS[1024 * 1024], nodF[1024 * 1024];
}

2 个答案:

答案 0 :(得分:5)

使用MS编译器的Windows上的默认堆栈大小为1 MiB。你在堆栈上放置了几个拥有数百万个整数和双精度的数组。

你说你的堆栈大小增加到4GB。在这种情况下,以下内容变得相关:

  

保留的内存大小表示虚拟内存中的总堆栈分配。因此,保留大小限于虚拟地址范围。 最初提交的页面在引用之前不使用物理内存;但是, 它们会从系统总提交限制中删除页面,该限制是页面文件的大小加上物理内存的大小 。系统会根据需要从保留的堆栈内存中提交其他页面,直到堆栈达到保留大小减去一页(用作防护页面以防止堆栈溢出)或系统内存太低以至于操作失败。 (强调我的

此外,Intel's notes list

  

鉴于这些定义,下面列出了Windows的32位和64位变体的限制:

     

32位

     
      
  • 堆栈数据 - 1GB(堆栈大小由链接器设置,默认值为1MB。可以使用链接器属性系统&gt;堆栈保留大小来增加)
  •   
     

...

     

64位

     
      
  • 堆栈数据 - 1GB(堆栈大小由链接器设置,默认值为1MB。可以使用链接器属性系统&gt;堆栈保留大小来增加)
  •   
     

...

     

请注意,静态和堆栈数据的限制在32位和64位变体中都是相同的。 这是由于Windows Portable的格式可执行(PE)文件类型,用于描述链接器列出的EXE和DLL。它具有用于图像部分偏移和长度的32位字段,并且未针对64位Windows变体进行扩展。与32位Windows一样,静态数据和堆栈共享相同的前2GB地址空间。 (强调我的

最后,仔细查看帖子开头的说明:

  

但错误继续在此行的chkstk.asm处停止

99       sub     eax, _PAGESIZE_         ; decrease by PAGESIZE

为了更仔细地观察这个,我编写了一个小程序,它会产生与你相同的效果(它只能用32位构建“工作” - 我不知道需要做什么来导致64-位可执行崩溃):

#include <stdio.h>
#include <stdlib.h>
#define MYSIZE (8 * 1024 * 1024)

int main(void) {
    int x[MYSIZE];
    x[MYSIZE - 1] = rand();
    printf("%d\n", x[MYSIZE - 1]);
    return 0;
}

我将链接器选项中的堆栈大小设置为4294967296,然后在调试器下运行程序。它因堆栈溢出而崩溃,并按照您观察到的相同指令断开。在堆栈检查代码中向上滚动,我注意到以下注释:

; Handle allocation size that results in wraparound.
; Wraparound will result in StackOverflow exception.

据我所知,例程试图一次将堆栈顶部向下移动PAGESIZE以保留适用的堆栈大小。

因此,尝试将堆栈大小设置为4 GB似乎是您当前问题的根本原因。您可以尝试将其设置为1 GB,这可能会解决该问题。实际上,我将上面程序的堆栈大小更改为1073741823= 1024 * 1024 * 1024 - 1),并且我没有得到堆栈溢出。我认为link至少没有警告堆栈大小值无效的事实是bug

实际上,查看使用/STACK:1000000000构建的可执行文件的hexdump并将其与使用/STACK:4294967296构建的可执行文件进行比较会突出显示问题:

00000150: 0000 0000 0300 4081 00ca 9a3b 0010 0000  ......@....;....
00000150: 0000 0000 0300 4081 0000 0000 0010 0000  ......@.........

请注意,0x3b9aca00的十六进制为1,000,000,000。查看header format,这些是指long SizeOfStackReserve;条目。也就是说,当您将堆栈大小设置为4 GB(实际上,任何高于0xfffffffc)的值时,都会将其设置为零。

虽然将堆栈大小设置为较大但仍然不支持的大小会导致在可执行标头中设置正值,例如:

cl main.c /link /STACK:0xdeadbead
xxd main.exe |more
...
00000150: 0000 0000 0300 4081 b0be adde 0010 0000  ......@.........
...

无法运行生成的可执行文件:

C:\...> main
Not enough storage is available to process this command.

然而,即使将堆栈设置为较小但仍然较大的大小可能会使程序运行,依赖于大堆栈并不一定是个好主意。直接的替代方法是使用malloc在堆上分配这些数组并记住释放它们。

即代替

int    Enod[8 * 1024 * 1024];

您需要声明int *Enod,然后使用

为数组分配内存
Enod = malloc(8 * sizeof(*Enod) * 1024 * 1024);
/* remember to check that Enod is not NULL */

this question的答案讨论了为什么堆栈受到更多约束。

此外,您的代码将受益于使用定义替换具有有意义的助记符的任意外观数字。

答案 1 :(得分:3)

使用malloc'd数组替换你的堆栈数组。繁荣!问题解决了。

当然,你可能仍然会耗尽内存,但至少你不必为了避免这种情况而努力工作,并且它是堆内存,IMO更容易获得更多。

你也可以预先分配一些malloc数组,这样代码就不会为每个函数调用做太多工作。