为啥函数执行后没有释放堆上的元素?

Posted

技术标签:

【中文标题】为啥函数执行后没有释放堆上的元素?【英文标题】:Why aren't elements on the heap released after the function?为什么函数执行后没有释放堆上的元素? 【发布时间】:2020-07-22 20:24:36 【问题描述】:

有人告诉我'是的。 node* new_node = new node; 在堆上分配一个节点,而函数内部的node new_node; 在堆栈上分配一个节点。如果节点相互指向,它仍然是一个链表。请注意,当函数结束时,堆栈分配的东西会自动释放。这就是为什么在堆上分配更方便。'

这是什么意思?有人可以详细说明吗?

【问题讨论】:

这可能有助于理解堆栈和堆之间的区别...gribblelab.org/CBootCamp/7_Memory_Stack_vs_Heap.html 【参考方案1】:

很长的答案。

自动存储时长

“堆栈”变量(更恰当地称为具有自动存储持续时间的实体)在您离开声明它们的范围后立即被销毁。 (即他们被“清理”并释放他们的内存)

void my_function() 
  node node1;
  if (1 == 1) 
      node node2;
      node* node3_ptr = new node; // this node is *not* cleaned up automatically
   // node2 is destructed now
  node node4;
 // node1 and node4 are destructed now

在上面的代码中,node1node4 在函数最外层范围的不同部分声明。当函数结束时,它们会“消失”。

函数是否运行到最后、提前返回、抛出异常都没有关系——如果函数结束,C++ 保证它们会被销毁。 (终止在其轨道上的应用程序是不同的。)

node2if 块内声明。当代码离开if 块时,它将被销毁——甚至在函数结束之前。

保证在完全可预测的时间自动销毁这些变量是 C++ 的最大优势之一。它被称为“确定性破坏”,这也是 C++ 是我首选语言的原因之一。

动态存储时长

“堆”变量(也就是具有“动态”存储位置的实体)比较棘手。

void my_leaky_function() 
  node* node5;

  new node;

  node* node6 = new node;

node5 仍然只是一个局部变量。它的类型是“指向节点的指针”而不仅仅是“节点”这一事实并不重要。它是一个具有自动持续时间的变量,它使用内存。它的大小是指针的大小(可能是 4 或 8 个字节 - 取决于您的平台),而不是节点的大小。该变量“消失”并在函数结束时恢复其内存。

new node; 在“空闲存储”(俗称“堆”)上分配内存。 new 返回一个指向已分配内存的指针,但此代码忽略了该指针。这里不涉及局部变量,函数结束时节点被销毁;

node* node6 = new node; 也为空闲存储上的节点对象分配了足够的空间——但这次new 返回的指针存储在名为node6 的局部变量中。注意:node6 是一个局部变量(存储指针,而不是节点),它具有自动存储持续时间。当函数结束时,node6 变量消失(并且它使用的几个字节内存被释放)。但是node6 也指向的节点 - 存储在免费存储中的节点 - 没有被破坏。

当这个函数结束时,它在空闲存储中留下了两个节点 - 因为它已经丢弃了指向每个节点的指针,所以任何人都无法删除它们。它有“内存泄露”。

为什么要使用动态存储?

C++ 承诺在您离开函数作用域时清理函数的自动存储值。这通常是您想要的。

有时一个函数需要创建一个比函数调用更有效的值 - 一个在函数退出时不能被销毁的值。

通常该值可以返回给调用者(没有new,没有指针),调用者可以用它做他们想做的事。通常,该值存储在某个集合中,例如已经为其分配内存的向量或数组。但是 - 在您的示例中,您想在链表或树(或类似的东西)中创建一个新节点,并且在函数结束时销毁该节点是没有意义的。

tl;博士;

所以

    如果一个值必须在创建它的函数结束后存在 它不只是返回给函数的调用者 并且它没有存储在其他容器的内存中

那么免费商店就是适合它的地方。

关于谁“拥有”该值并负责删除它 - 以及使用智能指针而不是原始指针 - 以及异常安全性 - 和和和 - 但这个答案已经是比我想要的大。所以让我以这个结束:

在学习 C++ 时,请使用指针。使用免费商店。用内存泄漏和双重删除烧伤自己。弄清楚你将如何解决这些问题。这为您理解稍后将使用的抽象奠定了良好的基础。

一旦你了解了指针和动态存储——一旦你了解了如何从头开始编写你自己的链表或二叉树——停止使用它们。在日常编码中,除非您是为容器库编写代码的专家,否则永远不要再使用 newdelete。绝对必要时使用智能指针,但尽量避免使用它们。

尽可能依靠自动存储期限。确定性破坏是你的朋友。这就是 C++ 与 C 的不同之处。这就是 C++ 与垃圾收集语言的不同之处。这就是为什么 C++ 仍然是编程语言之王之一。

【讨论】:

【参考方案2】:

您仍然可以在之前的方法调用中让指针引用相同的对象。

【讨论】:

以上是关于为啥函数执行后没有释放堆上的元素?的主要内容,如果未能解决你的问题,请参考以下文章

析构函数为啥能释放对象内存?

动态内存分配(c++)

为啥 Rust 不在匹配模式中执行隐式取消引用强制?

1-认识c指针

1-认识c指针

AWS lambda - 每次执行后释放 /tmp 存储