提问人:Default 提问时间:9/23/2023 最后编辑:trincotDefault 更新时间:11/11/2023 访问量:126
如何识别对象是否在堆栈内存或堆内存中
How to recognize if object is on the stack or heap memory
问:
我最近收到了数据结构课程的大学作业,这要求我在 C++ 中创建一个双向链表。
在处理我的双向链表时,我需要实现各种功能,但有一种方法特别引起我的注意,叫做“clear()”。此方法负责清除双向链表中的所有元素:
void clear(Node* head_ptr)
{
Node* previous_ptr = nullptr;
while(head_ptr != nullptr)
{
previous_ptr = head_ptr; // Store previous node.
head_ptr = head_ptr->next;
delete previous_ptr;
}
};
该方法非常简单;它只是遍历所有元素并为每个元素释放内存。然后,我在析构函数中调用此方法,如下所示:Node
~List()
{
clear_list(m_head_ptr);
};
然后我开始思考。如果我的节点元素在堆上,这种释放内存的方法很好,如下所示:
int main()
{
List list;
Node* node_1 = new Node(3, nullptr); // The tail node.
Node* node_2 = new Node(1, node_1);
Node* node_3 = new Node(5, node_2);
Node* node_4 = new Node(7, node_3); // The head node.
list.add(node_1);
list.add(node_2);
list.add(node_3);
list.add(node_4);
// Then do some stuff with the list...
} // The list goes out of scope and the destructor is called...
但是,一旦我在堆栈上创建节点
并传递指向堆栈对象的指针,这种情况就会中断,如下所示:
int main()
{
List list;
Node* node_1(3, nullptr); // The tail node.
Node* node_2(1, node_1);
Node* node_3(5, node_2);
Node* node_4(7, node_3); // The head node.
list.add(&node_1);
list.add(&node_2);
list.add(&node_3);
list.add(&node_4);
// Then do some stuff with the list...
} // The list goes out of scope and the destructor is called and the program crashes because it attempts to free stack objects...
原因是因为我正在尝试释放堆栈对象,这不是一个好主意。当然,我们通常不会使用基于堆栈的 s,因为我们通常希望我们的数据在创建它们的范围之外持久存在。然而,这让我想到了我的问题:Node
Node
我该如何应对?有没有办法检查内存中的某些节点
是否在我的函数的堆或堆栈上,然后相应地释放它?或者,有没有更好的方法来解决这个问题?
答:
最简单、最有效的解决方案通常是让列表本身管理节点的分配。用户甚至不需要知道类型存在,更不用说分配它们了。node
因此,对于用户来说,您在问题中显示的内容的等价物是这样的:
List list;
list.add_head(1);
list.add_head(3);
list.add_head(5);
list.add_head(7);
您通常还可以将项目添加到列表的末尾。每个(head 或 tail)通常都应该返回一个抽象类型(可能是指向节点的指针的包装器),因此您可以执行以下操作:add_tail
add
iterator
auto pos = list.add_head(7);
list.add_tail(5);
list.add_after(pos, 3);
...这将在开头添加 7,在结尾添加 5,并在 .3
7
这样,列表本身就会分配所有节点,并知道如何处置它们。您可以更进一步,让它将分配和处置委托给类。这当然是有用的,但可能有点超出了基本练习的意义(在实际使用中,您可能希望使用标准库中的容器 - 虽然标准库确实提供了单链表和双链表,但它们很少有用)。Allocator
评论
class node;
List
node *head;
node *tail;
node
List.cpp
node.hpp
由于您现在刚刚学习动态内存,因此您必须从头开始学习(必须有人编写库 - 有一天可能是你!
因此,使用链表的一个原因是,您不知道将提前添加多少个节点。同样,在链表上放置“MAX_SIZE”和基于堆栈的节点数据的实现也不好(MAX_SIZE是否足够大?对于链表的某些客户端来说,它是否太大了?
因此,链表几乎总是使用动态内存实现的。在 C 语言中,这将是 malloc/free。在 C++ 中,这将是 new/delete。最终,客户端代码将利用标准库模板化实现(就像许多人指向你的那样)。
因此,为了回答您的问题,在您的教育阶段,请专门为您的链表节点使用动态内存,使用 new/delete。
你很快就会知道为什么你(最终)会使用标准库实现,因为你必须了解使用/操作手工制作的链表时的所有边缘情况(取消引用已删除的指针、内存泄漏等)。
很多时候,你会从失败中学到更多(例如,如何使用调试器!
评论
Node* node_1(3, nullptr);
Node
new
delete
new
和delete
。您的方法应接受 .new Node()
add
std::unique_ptr<Node>
struct Bonus { int bonus; Node node; }; Bonus* bonus = new Bonus(); list.add(&bonus->node);
delete
std::unique_ptr
std::shared_ptr