为啥数组分配后堆这么大

Posted

技术标签:

【中文标题】为啥数组分配后堆这么大【英文标题】:Why is the heap after array allocation so large为什么数组分配后堆这么大 【发布时间】:2011-10-10 14:42:59 【问题描述】:

我有一个非常基本的应用程序,可以归结为以下代码:

char* gBigArray[200][200][200];
unsigned int Initialise()  
    for(int ta=0;ta<200;ta++)
        for(int tb=0;tb<200;tb++)
            for(int tc=0;tc<200;tc++)
                gBigArray[ta][tb][tc]=new char;
    return sizeof(gBigArray);

该函数返回 32000000 字节的预期值,大约为 30MB,但在 Windows 任务管理器中(并且假定它不是 100% 准确)给出的内存(私有工作集)值为大约 157MB。我已通过 SysInternals 将应用程序加载到 VMMap 并具有以下值:

我不确定 Image 是什么意思(列在 Type 下),尽管它的值与我的预期无关。真正让我失望的是堆值,这是明显的巨大尺寸的来源。

我不明白为什么会这样?根据this answer,如果我理解正确,gBigArray 将被放置在数据或 bss 段中 - 但是我猜测每个元素都是一个未初始化的指针,它将被放置在 bss 段中。那么为什么堆值会比所需的值大很多呢?

【问题讨论】:

new char - 你没有创建一个新的单字节字符。您正在创建一个 char 对象,该对象需要考虑元数据和其他开销。该数组只是指向这些对象的 200x200x200 指针,但这些对象本身显然占用了 157-30 = 127 MB 的内存。 为什么首先要有一个 char 指针数组?为什么不只是一个字符数组? 【参考方案1】:

如果您知道内存分配器的工作原理,这听起来并不傻。它们跟踪分配的块,因此有一个存储大小的字段以及指向下一个块的指针,甚至可能是一些填充。一些编译器在调试版本中在分配区域周围放置保护空间,因此如果您在分配区域之外或之前写入,当您尝试释放分配的空间时,程序可以在运行时检测到它。

【讨论】:

【参考方案2】:

您一次分配一个字符。每次分配通常都会产生空间开销

将内存分配到一大块(或至少几块)

【讨论】:

【参考方案3】:

不要忘记char* gBigArray[200][200][200];200*200*200=8000000 指针分配空间,每个字的大小。在 32 位系统上是 32 MB。

再添加一个8000000char,再增加8MB。由于您是一个一个地分配它们,因此可能无法按每个项目一个字节分配它们,因此它们可能还会占用每个项目的字大小,从而导致另一个 32MB(32 位系统)。

其余的可能是开销,这也很重要,因为 C++ 系统必须记住分配给 new 的数组包含多少元素 delete []

【讨论】:

除非他在这里没有调用new[],所以调用delete[]是未定义的。 delete 的非数组版本通过传入的类型知道大小。【参考方案4】:

哇!如果面对该代码,我的嵌入式系统的东西会翻滚而死。每个分配都有相当多的额外信息与之相关联,或者间隔固定大小,或者通过链表类型对象进行管理。在我的系统上,这个 1 char new 将成为一个小对象分配器的 64 字节分配,这样管理将在 O(1) 时间内完成。但是在其他系统中,这很容易使您的记忆变得非常糟糕,使后续的新操作和删除操作运行得非常缓慢 O(n) 其中 n 是它跟踪的事物的数量,并且随着时间的推移通常会给应用程序带来厄运,因为每个字符都会变成至少 32 字节的分配并被放置在内存中的各种小孔中,从而使您的分配堆超出您的预期。

如果您需要使用放置 new 或其他指针技巧,请进行一次大分配并将 3D 数组映射到它上面。

【讨论】:

【参考方案5】:

一次分配 1 个字符可能更昂贵。每个分配都有元数据标头,因此一个字符的 1 个字节小于标头元数据,因此您实际上可以通过进行一次大分配(如果可能)来节省空间,这样您就可以减轻每个具有自己元数据的单独分配的开销。

【讨论】:

【参考方案6】:

也许这是内存跨度的问题?值之间的差距有多大?

【讨论】:

【参考方案7】:

30 MB 用于 指针。其余的用于您使用new 调用分配的存储空间,指针指向to。出于各种原因,编译器可以分配多个字节,例如对齐字边界,或者提供一些增长空间以防您以后需要它。如果您想要 8 MB 的字符,请将 * 从声明中删除为 gBigArray

【讨论】:

【参考方案8】:

将上述帖子编辑为社区 wiki 帖子

正如下面的答案所说,这里的问题是我正在创建一个新的 char 200^3 次,虽然每个 char 只有 1 个字节,但堆上的每个对象都有开销。似乎为所有字符创建一个字符数组会将内存降低到更可信的水平:

char* gBigArray[200][200][200];
char* gCharBlock=new char[200*200*200];
unsigned int Initialise()  
    unsigned int mIndex=0;
    for(int ta=0;ta<200;ta++)
        for(int tb=0;tb<200;tb++)
            for(int tc=0;tc<200;tc++)
                gBigArray[ta][tb][tc]=&gCharBlock[mIndex++];
    return sizeof(gBigArray);

【讨论】:

以上是关于为啥数组分配后堆这么大的主要内容,如果未能解决你的问题,请参考以下文章

为啥在单个语句中分配动态对象的成员变量会导致 PHP 中的语法错误?

为啥分配堆内存比分配堆栈内存快得多?

如何在 C++ 中分配一个大的动态数组?

为啥要用Hash

为啥我在 C 中的多维动态分配不起作用?

JAVA里String数组在内存分配中分配的空间每个占几个字节?