理解内存分配

3

我正在尝试了解std::list数据结构如何分配内存。我创建了一个小测试程序。

#include <cstdio>
#include <vector>
#include <iostream>
#include <string>
#include <list>

class MyClass
{
public:

 MyClass();
~MyClass(){}

private:
std::list<unsigned char> numlist;
};

MyClass::MyClass()
{
numlist.push_back(1);
}

int main()
{ 

 MyClass c; // instantiate
}

我在valgrind中运行了上面的片段

$valgrind --leak-check=full ./indepth 
==32330== HEAP SUMMARY:
==32330==     in use at exit: 0 bytes in 0 blocks
==32330==   total heap usage: 1 allocs, 1 frees, 24 bytes allocated

请帮我理解为什么在这里分配了24个字节。

请检查您的示例,确保没有包含 #include <iostream>。同时请注意,MyClass c; 存储在栈中,首先不进行动态分配。我认为该语句至少会被优化掉。 - πάντα ῥεῖ
@PeteBecker 哎呀,我没有注意到那个。但是不应该分配超过1个字节的空间吧? - πάντα ῥεῖ
@πάνταῥεῖ:iostream与分配24字节无关。 - hAcKnRoCk
2
@πάνταῥεῖ - 根据列表的实现方式,它会分配一个1 节点,该节点将具有两个指针和一个int值。 - Pete Becker
@hAcKnRoCk,所以我假设你已经验证过了?仅包含<iostream>就需要创建std::coutstd::cinstd::cerr的实例。 - πάντα ῥεῖ
显示剩余3条评论
4个回答

2

不了解特定编译器及其选项,很难给出明确的答案。但是对于调用 push_back 的程序,它将为创建的列表元素分配一个节点,该节点将具有两个指针(一个用于下一个节点,另一个用于前一个节点),以及存储值的 int。要获取这些部分的详细信息,请运行此程序:

#include <iostream>

int main() {
    std::cout << sizeof(int*) << ", " << sizeof(unsigned char) << '\n';
    return 0;
}

那会告诉你指针有多大,以及int有多大。

编译器是 gcc version 4.9.2 20150212 (Red Hat 4.9.2-6) (GCC)。我使用以下命令进行编译:g++ -std=c++11 -g indepth.cpp -o indepth - hAcKnRoCk
明白了。所以,指针的大小是8字节。因此,2个指针 = 16字节 + 8字节无符号字符(即整数类型)。因此,总共是24字节。 - hAcKnRoCk
@hAcKnRoCk - 哎呀,抱歉,我的回答中应该用“unsigned char”代替“int”。这是我疏忽了。一个unsigned char可能是1个字节,但节点大小可能已经被舍入为8的倍数。 - Pete Becker
是的,Pete,你的答案还是帮了我。我明白了。在你的片段中,我将int编辑为unsigned char。 - hAcKnRoCk
如果我将 std::list<int> 替换为 std::list<string> 并推入单个字符,则会分配 55 字节。现在,我想知道这 39 字节来自哪里。 - hAcKnRoCk
@hAcKnRoCk 不能确定,但如果我要为字符串的内部缓冲区分配存储空间,我不会逐个字符地进行。我会分配一个合理大小的块来减少重新分配的频率。40字节对我来说听起来很合理。 - user4581301

2
每个元素在std::list中的确切大小取决于STL实现,但通常它被实现为双向链表,这意味着每个节点(代表元素)至少需要3个数据成员(前一个节点,后一个节点来实现双向链接列表以及数据)。
在gcc实现中,文件std_list.h类_List_node包含此节点的定义。2个指针(32位系统中为2 * 4字节或64位系统中为2 * 8字节)加上sizeof(data)。
除此之外,列表可能还必须维护一些其他内部信息,例如大小(自C++11起,标准要求std::list::size()是O(1))。
注释:
STL实现=标准库实现(随编译器提供或配置为由编译器使用的C++标准库的实现)
- 在gcc的情况下是libstdc++ - 在clang的情况下可以是libc++(clang实现)或libstdc++(gcc实现) - 在VC++的情况下,是由Dinkumware提供的内部实现。
还有其他STL实现,例如STLPort等...这些细节取决于实现。确保的唯一方法是查看STL实现的特定版本的代码,而确切的大小随时可能会更改。

非常好的回答。您能否编辑一下,更详细地解释一下“std::list中每个元素的确切大小取决于STL实现”的原因?为什么?您是指每个元素的确切大小还是指类型? - hAcKnRoCk

1
请帮我理解为什么在这里分配了24个字节。
这些细节是与实现相关的,因此确定的答案在您的编译器和标准库的源代码或文档中。内存使用在另一个实现中可能会有所不同。
但是,可以根据双向链表的简单实现做出合理的猜测:通常无法在编译时知道节点数,因此它们可能需要动态分配。节点应具有指向下一个和上一个节点以及存储的数据的指针。那就是1个int和2个指针。在特定的体系结构中,指针的大小很可能是8个字节。int通常也适合于8个字节,但即使它更小,节点也必须填充到最近的8字节边界,以满足指针的对齐要求。这使得这个想象中的简单实现需要24个字节。您的标准库使用的真实实现可能类似。

0

std::list容器是一个链表,因此它分配包含不仅您的元素,还有两个指针的节点:一个指向下一个节点,一个指向前一个节点。

如果您想要一个更节省内存的容器,请考虑使用std::vector并使用std::vector::reserve函数预先分配内存。


1
向量在大多数情况下更加节省内存和时间。链表本质上是一种分散的结构,会导致缓存未命中和基于预测的优化的可怕损失。详情请阅读这里:https://isocpp.org/blog/2014/06/stroustrup-lists - user4581301

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