C++ 为什么树结构占用的空间大于其节点的总和

C++ 为什么树结构占用的空间大于其节点的总和,c++,C++,填充以下n元树数据结构将创建64'570'080个节点,对于x64构建,每个节点应占用约1480mb内存空间24字节。但正如VisualStudio和任务管理器所示,该程序的实际内存占用约为1900mb。当我不填充一棵树,而是将相同数量的节点推到一个向量中时,封装外形会达到预期的1480mb 为什么树比向量中相同数量的节点占用更多的空间?我如何修复它?我使用最新的MSVC编译器 结构体类型 { 公众: 无效添加孩子 { 如果第一个\u子项\u==nullptr { 第一个孩子=std::使孩子唯

填充以下n元树数据结构将创建64'570'080个节点,对于x64构建,每个节点应占用约1480mb内存空间24字节。但正如VisualStudio和任务管理器所示,该程序的实际内存占用约为1900mb。当我不填充一棵树,而是将相同数量的节点推到一个向量中时,封装外形会达到预期的1480mb

为什么树比向量中相同数量的节点占用更多的空间?我如何修复它?我使用最新的MSVC编译器

结构体类型 { 公众: 无效添加孩子 { 如果第一个\u子项\u==nullptr { 第一个孩子=std::使孩子唯一; 第一个子项->父项=this; } 其他的 { Node*next=first\u child\uu.get; 而next->next\u sibling\u!=nullptr { 下一个=下一个->下一个兄弟姐妹获取; } next->next\u sibling\u=std::使\u唯一; next->next\u sibling\u->parent\u=此; } } 类节点语言; 无语儿童; Node*GetNextSibling{return next_sibling_u.get;} 私人: //指向根节点的父节点的指针。 节点*父节点=nullptr; //指向叶节点的第一个子.nullptr的指针。 std::唯一\u ptr第一\u子\uu; //指向下一个同级的指针。如果没有其他同级,则为null ptr。 std::唯一\u ptr下一个\u兄弟\uu; }; 类节点算子 { 公众: nodeItemeratorNode*节点:节点\节点{} 节点*运算符*{返回节点} 节点*运算符->{返回节点} bool操作符==NodeIterator&other{return node_==other.node_;} 布尔运算符!=NodeIterator&other{return node_!=other.node_;} void操作符+++{node\uux=node\ux->GetNextSibling;} 私人: Node*Node_; }; 类节点::节点名称 { 公众: NodeIterator begin{return NodeIterator节点} NodeIterator end{return NodeIteratornullptr;} 私人: noderAnode*节点:节点\节点{} Node*Node_; 好友类节点; }; Node::NodeRange Node::GetChildren{return first_child_u.get;} 定义最大深度16 定义分枝因子3 std::唯一的ptr树; 大小\u t节点计数=0; void PopulateNode&node,int currentDepth=0 { 如果currentDepth==最大深度返回; 对于大小,i=0;i<分支因子;i++ { node.AddChild; nodeCount++; } 对于节点*子节点:Node.GetChildren { 填充*子项,currentDepth+1; } } int main { tree=std::使_唯一; 填充*tree.get;
std::cout由于每个树节点都是单独分配的,因此每个堆内存分配都有开销,堆分配器将其内务管理信息与每个分配的块一起存储。在GNU malloc开销为8字节的64位系统上,MSVC运行时库可能有不同的非零开销,但看起来是8字节It’好的。详情请参阅

最小化开销的一种方法是从一个大的预先分配的数组中分配树节点

使用std::unique\u ptr存储子节点可能会由于递归调用而导致堆栈溢出:~Node调用first\u child\u->~ std::unique\u ptr调用~Node,后者调用first\u child\u->~ std::unique\u ptr等等,这可能会使堆栈溢出


一种解决方案是将第一个子节点和下一个同级节点作为普通节点*指针,并在~Tree中实现类树和代码,该类树和代码在树中运行而不递归并手动销毁树节点。在这种情况下,树拥有自己的节点。

由于每个树节点都是单独分配的,因此每个堆内存分配都会有开销,堆分配cator将其内务管理信息与每个分配的块一起存储。在GNU malloc开销为8字节的64位系统上,MSVC运行时库可能有不同的非零开销,但看起来也是8字节。有关更多详细信息,请参阅

最小化开销的一种方法是从一个大的预先分配的数组中分配树节点

使用std::unique\u ptr存储子节点可能会由于递归调用而导致堆栈溢出:~Node调用first\u child\u->~ std::unique\u ptr调用~Node,后者调用first\u child\u->~ std::unique\u ptr等等,这可能会使堆栈溢出


一种解决方案是让第一个子节点和下一个同级节点成为普通节点*指针,并在~Tree中实现类树和代码,该类树和代码不递归地遍历树并手动销毁树节点。在这种情况下,树拥有自己的节点。

最后一段可能会扩展信息,这并不意味着unique\ptr在这方面是错误的案例,但它需要编写一个析构函数来处理它。如前所述,请扩展容器实现中的递归销毁问题,并在您的帖子中提出解决方案。请。@Red.Wave
我认为这并不需要实现,因为这与解释内存使用差异的问题无关。值得一提的是,在这种情况下使用unique_ptr并没有错,但需要实现一个析构函数来处理这个问题。@t.niese如果有人问这样一个关于堆的问题,他很可能在堆栈方面也有困难。但如果他不知道该问什么,这并不意味着我们不应该提及什么是合适的。我不擅长写回信;但是,既然你的回答是被接受的,也是唯一的答案——这恰好也是一个非常好的回答,那么完美的回答是值得的。机器人的解决方案是让第一个孩子和下一个兄弟姐妹成为普通节点*这是一个可怕的建议,为什么要放弃unqiue\u ptr的好处而支持手动内存管理呢。它不递归地遍历树并手动销毁树节点。这是真的,但这可以用unqiue_ptr以同样的方式完成,并且您不必手动进行内存管理。最后一段可能会用信息进行扩展,这并不意味着unique_ptr在这种情况下是错误的,但它需要编写一个处理它的析构函数。正如已经建议的那样,请在您的帖子中扩展容器实现中的递归销毁问题和建议的解决方案。@Red.Wave我认为不需要实现,因为这与解释内存使用差异的问题无关。值得一提的是,在这种情况下使用unique_ptr并没有错,但需要实现一个析构函数来处理这个问题。@t.niese如果有人问这样一个关于堆的问题,他很可能在堆栈方面也有困难。但如果他不知道该问什么,这并不意味着我们不应该提及什么是合适的。我不擅长写回信;但是,既然你的回答是被接受的,也是唯一的答案——这恰好也是一个非常好的回答,那么完美的回答是值得的。机器人的解决方案是让第一个孩子和下一个兄弟姐妹成为普通节点*这是一个可怕的建议,为什么要放弃unqiue\u ptr的好处而支持手动内存管理呢。它不递归地遍历树并手动销毁树节点。这是真的,但这可以通过使用unqiue_ptr以相同的方式完成,并且您不必手动进行内存管理。