堆上的内存如何被耗尽?

6

我一直在测试自己的代码,看看需要多少分配内存才能耗尽堆内存或自由存储。但是,除非我的代码测试有误,否则我得到的结果完全不同,无法确定可以放入堆上的内存量。

我正在测试两个不同的程序。第一个程序在堆上创建向量对象。第二个程序在堆上创建整数对象。

以下是我的代码:

#include <vector>
#include <stdio.h>

int main()
{
    long long unsigned bytes = 0;
    unsigned megabytes = 0;

    for (long long unsigned i = 0; ; i++) {

        std::vector<int>* pt1 = new std::vector<int>(100000,10);

        bytes += sizeof(*pt1);
        bytes += pt1->size() * sizeof(pt1->at(0));
        megabytes = bytes / 1000000;

        if (i >= 1000 && i % 1000 == 0) {
            printf("There are %d megabytes on the heap\n", megabytes);
        }

    }
}

这段代码在出现bad_alloc错误前的最终输出结果是:“堆上有2000兆字节”。在第二个程序中:
#include <stdio.h>

int main()
{
        long long unsigned bytes = 0;
        unsigned megabytes = 0;

        for (long long unsigned i = 0; ; i++) {

           int* pt1 = new int(10);

           bytes += sizeof(*pt1);
           megabytes = bytes / 1000000;

           if (i >= 100000 && i % 100000 == 0) {
              printf("There are %d megabytes on the heap\n", megabytes);
        }

    }
}

在遇到bad_alloc错误之前,此代码的最终输出为:“堆上有511兆字节”。

两个程序的最终输出差别很大。我是否对自由存储区有什么误解?我认为两种结果应该差不多。


不确定是否相关,但是Linux会惰性地分配内存。 - P. Dmitry
@P.Dmitry,这不应该有影响:在此分配的数据由10初始化。 - Ruslan
@Ruslan 大部分正确,但编译器可以看到这个值未被使用,因此可以 (?) 删除此初始化。 - P. Dmitry
1
@P.Dmitry,这是在调试版本中发生的(刚测试过)。我不会期望在这种情况下进行这样的优化。 - Ruslan
顺便说一句,我在第一种情况和第二种情况之间也有大约4倍的差异(4000与1071)。这可能是由于管理4字节与100000字节分配块的开销所致。 - Ruslan
我只想提一下,你第一次计算 MB 的结果可能会有误,因为除非在你的机器上 ints 至少占用 10个字节,否则任何小于一百万的字节数都会使初始计算为0。当然,我是在谈论第一个向量示例,但我认为你在第二个示例中失去了更多的精度。 - smac89
2个回答

3

很可能{{link1:您的平台上由new返回的指针是16字节对齐的}}。

如果int4字节,这意味着对于每个new int(10),您将获得四个字节并使12个字节无法使用。

仅这一点就可以解释为什么从小型分配中获得500MB可用空间,而从大型分配中获得2000MB的差异。

除此之外,还需要跟踪已分配块的开销(至少要跟踪它们的大小以及它们是自由的还是正在使用)。这非常特定于系统的内存分配器,但也会产生每个分配的开销。请参见https://sourceware.org/glibc/wiki/MallocInternals中的“什么是Chunk”以了解glibc的分配器的说明。


使用malloc分配16字节的块(并为它们计算)仍然会给我留下约2倍的差异:2142 MB vs 4000 MB。 - Ruslan

0
首先,您必须了解操作系统将内存以相当大的内存块(称为页面)分配给进程(这是硬件属性)。页面大小约为4-16 kB。
现在,标准库尝试以高效的方式使用内存。因此,它必须找到一种方法将页面切成较小的片段并管理它们。为此,必须维护有关堆结构的一些额外信息。
这里有一个很酷的Andrei Alexandrescu cppcon talk,大致说明了它的工作原理(省略了有关页面管理的信息)。
所以,当您分配大量小对象时,有关堆结构的信息就相当大了。另一方面,如果您分配较少数量的较大对象,则更有效率 - 跟踪内存结构时浪费的内存较少。
还要注意,根据堆策略,有时(当请求一小块内存时)浪费一些内存并返回比请求的内存更大的内存大小更有效率。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接