【问题标题】:Unusual heap size limitations in VS2003 C++VS2003 C++ 中不寻常的堆大小限制
【发布时间】:2011-01-29 00:14:32
【问题描述】:

我有一个使用大量数据数组的 C++ 应用程序,并且在测试时注意到它内存不足,但仍有大量可用内存。我已将代码简化为示例测试用例,如下所示;

void    MemTest()
{
    size_t Size = 500*1024*1024;  // 512mb
    if (Size > _HEAP_MAXREQ)
        TRACE("Invalid Size");
    void * mem = malloc(Size);
    if (mem == NULL)
        TRACE("allocation failed");

}

如果我创建一个新的 MFC 项目,包含这个函数,并从 InitInstance 运行它,它在调试模式下工作正常(按预期分配内存),但在释放模式下失败(malloc 返回 NULL)。单步发布到 C 运行时,我的函数被内联我得到以下

// malloc.c

void * __cdecl _malloc_base (size_t size)

{
        void *res = _nh_malloc_base(size, _newmode);

        RTCCALLBACK(_RTC_Allocate_hook, (res, size, 0));

        return res;
}

调用 _nh_malloc_base

void * __cdecl _nh_malloc_base (size_t size, int nhFlag)
{
        void * pvReturn;

        //  validate size
        if (size > _HEAP_MAXREQ)
            return NULL;
'
'

And (size > _HEAP_MAXREQ) 返回 true,因此我的内存没有被分配。将手表大小返回到预期的 512MB,这表明该程序正在链接到具有小得多的 _HEAP_MAXREQ 的不同运行时库。 Grepping _HEAP_MAXREQ 的 VC++ 文件夹显示预期的 0xFFFFFFE0,所以我无法弄清楚这里发生了什么。任何人都知道会导致此问题的任何 CRT 更改或版本,还是我遗漏了更明显的东西?

编辑:正如 Andreas 所建议的,在这个装配视图下查看它会显示以下内容;

--- f:\vs70builds\3077\vc\crtbld\crt\src\malloc.c ------------------------------
_heap_alloc:
0040B0E5  push        0Ch  
0040B0E7  push        4280B0h 
0040B0EC  call        __SEH_prolog (40CFF8h) 
0040B0F1  mov         esi,dword ptr [size] 
0040B0F4  cmp         dword ptr [___active_heap (434660h)],3 
0040B0FB  jne         $L19917+7 (40B12Bh) 
0040B0FD  cmp         esi,dword ptr [___sbh_threshold (43464Ch)] 
0040B103  ja          $L19917+7 (40B12Bh) 
0040B105  push        4    
0040B107  call        _lock (40DE73h) 
0040B10C  pop         ecx  
0040B10D  and         dword ptr [ebp-4],0 
0040B111  push        esi  
0040B112  call        __sbh_alloc_block (40E736h) 
0040B117  pop         ecx  
0040B118  mov         dword ptr [pvReturn],eax 
0040B11B  or          dword ptr [ebp-4],0FFFFFFFFh 
0040B11F  call        $L19916 (40B157h) 
$L19917:
0040B124  mov         eax,dword ptr [pvReturn] 
0040B127  test        eax,eax 
0040B129  jne         $L19917+2Ah (40B14Eh) 
0040B12B  test        esi,esi 
0040B12D  jne         $L19917+0Ch (40B130h) 
0040B12F  inc         esi  
0040B130  cmp         dword ptr [___active_heap (434660h)],1 
0040B137  je          $L19917+1Bh (40B13Fh) 
0040B139  add         esi,0Fh 
0040B13C  and         esi,0FFFFFFF0h 
0040B13F  push        esi  
0040B140  push        0    
0040B142  push        dword ptr [__crtheap (43465Ch)] 
0040B148  call        dword ptr [__imp__HeapAlloc@12 (425144h)] 
0040B14E  call        __SEH_epilog (40D033h) 
0040B153  ret              
$L19914:
0040B154  mov         esi,dword ptr [ebp+8] 
$L19916:
0040B157  push        4    
0040B159  call        _unlock (40DDBEh) 
0040B15E  pop         ecx  
$L19929:
0040B15F  ret              
_nh_malloc:
0040B160  cmp         dword ptr [esp+4],0FFFFFFE0h 
0040B165  ja          _nh_malloc+29h (40B189h) 

寄存器如下;

EAX = 009C8AF0 EBX = FFFFFFFF ECX = 009C8A88 EDX = 00747365 ESI = 00430F80 EDI = 00430F80 EIP = 0040B160 ESP = 0013FDF4 EBP = 0013FFC0 EFL = 00000206

所以比较似乎与正确的常数相反,即@040B160 cmp dword ptr [esp+4],0FFFFFFE0h,还有 esp+4 = 0013FDF8 = 1F400000(我的 512mb)

第二次编辑:根据 Andreas 的帖子,问题实际上出在 HeapAlloc 中。使用 HeapCreate 和 HeapAlloc 更改为大型对象的新单独堆并没有帮助缓解问题,也没有尝试将 VirtualAlloc 与各种参数一起使用。一些进一步的实验表明,在分配一大块连续内存失败的情况下,两个较小的块产生相同的总内存是可以的。例如如果 300MB malloc 失败,则 2 x 150MB malloc 可以正常工作。所以看起来我需要一个新的数组类,它可以存在于许多大内存片段中,而不是单个连续块中。不是什么大问题,但在这个时代,我对 Win32 的期望会更高一些。

上次编辑:以下产生了 1.875GB 的空间,尽管不连续

#define TenMB 1024*1024*10

void    SmallerAllocs()
{

    size_t Total = 0;
    LPVOID  p[200];
    for (int i = 0; i < 200; i++)
    {
        p[i] = malloc(TenMB);
        if (p[i])
            Total += TenMB; else
            break;
    }
    CString Msg;
    Msg.Format("Allocated %0.3lfGB",Total/(1024.0*1024.0*1024.0));
    AfxMessageBox(Msg,MB_OK);
}

【问题讨论】:

  • 如果你看反汇编,_HEAP_MAXREQ&gt;-comparison中使用了什么值?

标签: c++ visual-c++ malloc


【解决方案1】:

可能是调试器在发布模式下对你耍花招?在发布模式下,单步执行和变量值都不可靠。

我在 VS2003 中以发布模式尝试了您的示例,当单步执行时,它起初看起来就像代码落在 return NULL 行上,但是当我继续执行时,它最终会继续进入 HeapAlloc,我猜正是这个函数失败了,查看反汇编if (size &gt; _HEAP_MAXREQ) 会发现以下内容:

00401078  cmp         dword ptr [esp+4],0FFFFFFE0h 

所以我认为_HEAP_MAXREQ 没有问题。

【讨论】:

  • 我尝试追踪返回 NULL,并得到了类似的结果,失败发生在我的原始应用程序中的“return HeapAlloc(_crtheap, 0, size);”中在 malloc.c 中,基本上显示我的测试程序有缺陷。我将尝试另一个测试程序并重新发布,因为问题仍然出现在我的主应用程序中。
  • @Shane 512 MB 是相当多的内存,当然有可能您的进程虚拟地址空间中没有这么多 连续 内存。 (分配可能在调试模式下以不同的方式放置,解释了它为什么在那里工作)。
  • 我自己也得出了这个结论。我尝试了另一个测试程序,每个测试程序有 2 个 512mb 的 malloc,它有效,而我的应用程序试图分配 2 300mb 块并失败。我的猜测是这与堆碎片有关,我将不得不回顾我如何在我的应用程序中使用堆。感谢所有反馈。
  • @Shane 您将永远无法分配 2300 MB,限制为 ~2 GB(在 x86 上),请参阅:blogs.msdn.com/oldnewthing/archive/2004/08/17/…
  • @Andreas Brinck:我很确定他的意思是 2 x 300 MB,而不是 2,300 MB。 ;-)
猜你喜欢
  • 2010-12-25
  • 1970-01-01
  • 2011-07-31
  • 2010-09-11
  • 1970-01-01
  • 1970-01-01
  • 2018-07-20
  • 2020-09-05
  • 2011-01-19
相关资源
最近更新 更多