我最近知道一个整数从内存中占用4个字节。
首先运行此代码,并测量内存使用情况:
int main()
{
int *pointer;
}
然后我修改了代码以分配 1000个整数变量。
int main()
{
int *pointer;
for (int n=0; n < 1000; n++)
{
pointer = new int ;
}
}
然后我决定使用 262,144个整数变量来消耗1MB的内存。
int main()
{
int *pointer;
for (int n=0; n < 262144; n++)
{
pointer = new int ;
}
}
令人惊讶的是,现在需要8MB
内存使用量,以整数数量呈指数增长 为什么会这样?
我在Kubuntu 13.04(amd64)
请给我一点解释。
谢谢!
注意:sizeof(integer)
会返回4
答案 0 :(得分:14)
单独分配的动态对象的内存不需要是连续的。事实上,由于new char[N]
的对齐要求(即在alignof(std::maxalign_t)
对齐,通常为16),标准内存分配器可能永远不会为了返回而返回 > 16字节对齐的内存。因此每个int
分配实际上消耗(至少)16个字节。 (分配器可能需要进一步的内存来进行内部簿记。)
道德当然是你应该使用std::vector<int>(1000000)
来理解一百万个动态整数。
答案 1 :(得分:10)
公共分配器中的非优化分配会带来一些开销。你可以想到两个“块”:一个INFO和一个STORAGE块。 信息块很可能就在您的STORAGE块前面。
所以,如果你分配了你的内存会有类似的东西:
Memory that is actually accessible
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
--------------------------------------------
| INFO | STORAGE |
--------------------------------------------
^^^^^^^^^
Some informations on the size of the "STORAGE" chunk etc.
此外,块将沿着某个粒度对齐(在int的情况下有点像16个字节)。
我会在MSVC12上写下这个样子,因为我现在可以对它进行测试。
让我们来看看我们的记忆。箭头表示16字节边界。
如果你分配一个4字节整数,你将在某个16字节边界(第二个边界之后的橙色方块)获得4个字节的内存。 在该块之前的16个字节(蓝色的)被占用以存储附加信息。 (我会在这里跳过诸如endianess之类的内容,但请记住,这会影响这种布局。)如果你在分配的内存前读取这个16字节块的前四个字节,你会发现分配的数量字节。
如果现在分配第二个4字节整数(绿色框),它的位置将至少是16字节边界的2倍,因为INFO块(黄色/红色)必须放在它前面,而不是右边界。红色块再次是包含字节数的那个。
您可以很容易地看到:如果绿色块早先是16个字节,则红色和橙色块会重叠 - 不可能。
你可以自己检查一下。我正在使用MSVC 2012,这对我有用:
char * mem = new char[4096];
cout << "Number of allocated bytes for mem is: " << *(unsigned int*)(mem-16) << endl;
delete [] mem;
double * dmem = new double[4096];
cout << "Number of allocated bytes for dmem is: " << *(unsigned int*)(((char*)dmem)-16) << endl;
delete [] dmem;
打印
Number of allocated bytes for mem is: 4096
Number of allocated bytes for dmem is: 32768
这是完全正确的。因此,在MSVC12的情况下,使用new的内存分配有一个额外的“INFO”块,其大小至少为16字节。
答案 2 :(得分:4)
您正在分配多个动态变量。每个变量包含4个字节的数据,但是内存管理器通常存储有关已分配块的一些附加信息,并且每个块应该对齐,这会产生额外的开销。
尝试pointer = new int[262144];
并查看差异。
答案 3 :(得分:2)
每次分配int
:
free
时有多少空间。 (这可以通过结合使用delete
运算符的空间知识来优化。但是,一般的内存分配例程通常与编译器的new
和delete
代码分开。)另一个可能的影响是,随着内存使用的增长,分配器会从系统请求并初始化更大的块。也许第一次使用初始内存池时,分配器会再请求16个页面。下一次,它请求32.下一次,64。我们不知道内存分配器从系统请求了多少内存实际上已用于满足您对int
个对象的请求。
不要动态分配许多小对象。改为使用数组。
答案 4 :(得分:1)
我认为这取决于编译器如何创建输出程序。
程序的内存使用包括程序的所有部分(如.text,其中包含程序的汇编指令),因此在加载时需要一些空间内存。
对于更多变量,当你分配一些内存(内存对齐)时,内存并不是真正连续的,所以它可能需要比你想象的更多的内存。
答案 5 :(得分:1)
两种解释:
new
时,执行动态分配。-g
编译器标志),则内存使用量可能会超出预期。答案 6 :(得分:1)
每个声明都会生成一个适合编译器对齐选项的新变量,它需要之间的空格(变量的起始地址应该是128或64或32(位)的倍数,这会导致许多变量的内存效率低下vs一个数组)。数组对于连续区域更有用。
答案 7 :(得分:1)
您正在分配的不仅仅是一个int,您还要分配一个具有开销的堆块(根据平台而不同)。有些东西需要跟踪堆信息。如果你改为分配一组int,你会发现内存使用量更符合你的预期。
答案 8 :(得分:1)
除了其他问题中提到的对齐和开销问题之外,这可能是由于C ++运行时请求从OS处理内存分配的方式。
当进程的数据部分填满时,运行时必须为进程分配更多的内存。它可能不会在相同大小的块中执行此操作。一种可能的策略是每次请求内存时,它会增加请求的数量(每次可能使堆大小加倍)。对于不占用大量内存的程序,此策略可以减少内存分配,但会减少大型应用程序请求新分配的次数。
尝试在strace
下运行您的计划并查找对brk
的来电,并注意每次请求的大小。