3

我直接从托管语言开始,几乎没有任何 C++ 经验,因此这个问题可能太基础了。

在像 .net 这样的托管语言中,GC 会释放内存。根据我的阅读,在 C++ 中,这是通过调用 delete 来完成的。但是它对释放内存有什么作用呢?它是否将内存位置的所有位设置为零?或者它是否以其他方式告诉操作系统该内存可供重用?

更新:我以前经历过这个,我知道 GC 是做什么的。但这不是我的问题。我不是想问 GC 是如何工作的。我想了解的是,您如何判断某些内存是空闲的?

4

6 回答 6

5

delete做三件不同的事情:

  1. 运行对象的析构函数(或数组中所有对象的析构函数delete[])。

  2. 将对象先前使用的内存块标记为空闲。

  3. 如果可能,通知操作系统一块内存可供其他程序使用。

你的问题是关于 #2 和 #3 在一起,但它们是非常不同的东西。要了解#2 的工作原理,请记住操作系统提供的(通常)单个“堆”被分割成不同大小的较小块。当您使用 分配一块内存时new,您将获得一个指向堆中先前空闲部分的指针,并且运行时执行必要的簿记,将该区域标记为不可用于进一步分配。delete做相反的事情:它执行将区域标记为再次可用的簿记,可选择将其与相邻的空闲区域合并以减少碎片。后续调用new将在寻找要返回的空闲内存时考虑该区域。

换句话说,问内存在被释放时会发生什么是错误的。真正的魔法发生在簿记领域!要了解通用分配器的实现,请谷歌实现 malloc。

至于#3,这是一个可选步骤,在许多情况下无法执行。只能“归还”恰好位于已分配堆末尾的已释放内存。位于大区域之后的单个分配将消除回馈的可能性。

于 2013-07-06T21:08:34.137 回答
2

在 C++ 中,如果您使用“new”分配内存,则操作系统将把这部分内存分配给该特定进程,直到您释放该内存或该进程退出。

如果为进程分配的内存的某些部分意味着操作系统不允许其他进程使用该部分内存,直到该进程释放该内存。在 C++ 中,您必须使用“删除”来释放内存。

通过释放内存部分,进程只是通知操作系统它不再使用这部分内存,以便操作系统可以在其他进程请求内存时分配该内存部分。在那种情况下,存储器部分的内容将不会改变。

于 2013-07-06T02:38:26.100 回答
1

不,删除指针不会将字节设置为零。这当然不在标准中,但这将是性能开销,严重的实现不会费心去做,当内存用于复杂对象(浮点数、对象、字符串等)时,它甚至没有意义

您可以随时尝试。声明一个指向 int 的指针,写入一个整数,删除指针。然后再次读取已删除指针的内容。内容一样吗?

int *ptr = new int;
*ptr = 13;
cout << "Before delete: " << *ptr << endl;
delete ptr;
cout << "After delete: " << *ptr << endl;

是的,可能会,但是 ptr 只是您在那里的一个悬空指针,内存已返回给系统,并且当您再次分配内存时它将可用,很可能当您分配另一个 int* 时,它将指向 ptr 的位置指着。

于 2013-07-06T20:28:39.427 回答
1

垃圾收集只是自动内存管理(所以你永远不需要delete任何东西,系统会为你处理它)。我不是 100% 是否将内存位置设置为 0,但我认为它不会,因为当你delete在 c++ 中时,这不会发生,它只是允许空间用于存储。在所有内容上写零效率低得多,也没有必要。这里有一些链接可能有助于更彻底地解释这一点:

垃圾收集和作用域在 C# 中是如何工作的?

http://en.wikipedia.org/wiki/Garbage_collection_(computer_science)

于 2013-07-06T01:02:10.777 回答
1

不,它不会将位设置为零。在一个非常简单的解释中,

首先,垃圾收集器必须确定的不是哪些对象不再可访问(“不可访问”),而是哪些对象仍可访问或可访问。它通过简单地列出所有对象来做到这一点。根是包含指向引用对象(堆上的对象)的指针的内存位置。然后,递归地,它将根引用的每个对象标记为“可达”,或者由已经标记为可达的对象的字段或属性引用的每个对象标记为“可达”。
根有四种。

  1. 包含引用对象的静态变量
  2. 任何当前活动线程的堆栈上的引用对象。
  3. 方法参数中的引用类型
  4. CPU 寄存器指向的引用对象。

在确定应用程序域中的任何代码仍然可以访问(可达)哪些引用对象之后,它会获取所有仍然可以访问的对象,如果它们之间的物理内存有任何间隙,它会通过移动一些对象来“整理”它们它们是连续的,然后它将表示“结束”和“使用”内存的指针设置到这个新的压缩碎片整理列表的末尾。所有新的内存分配,对于新实例化的对象,都是在这个指针位置之后立即从内存中分配的。

如果可达对象使用的内存没有间隙,它只是将指针重置为列表中最后一个可达对象的末尾。

于 2013-07-06T01:04:11.897 回答
1

在每个应用程序内部,动态内存由“堆”管理。当您的代码请求一块内存时,它会要求堆管理器分配一块内存,当您的应用程序释放该内存块时,它会将其返回给堆管理器。在传统应用程序中,您必须显式返回您分配的每个内存。否则你最终会耗尽内存。

在 C# 或 Java 等语言中,运行时提供了垃圾收集器。垃圾收集器会自动识别“无法访问”的内存块并释放它们。不可用内存块是不再被任何变量引用的块。例如,如果你有一个全局变量 p1 指向一块内存,因为 p1 是全局的,所以它对你的代码中的任何地方都是可见的,那么它总是可以访问的。因此它永远不会被垃圾收集器释放。另一方面,如果您的一个函数 Foo 中有局部变量 p2,则在 Foo 返回后不再可访问变量 p2。垃圾收集器能够识别这些变量并释放它们指向的任何内存块。

当应用程序/垃圾收集器与堆交互时,堆可能决定向操作系统请求更多内存或将其返回给操作系统。操作系统管理来自不同进程的所有这些内存请求,然后决定如何将实际的物理内存分配给不同的进程。

于 2013-07-06T01:07:12.570 回答