C# 如何从内存中清除引用类型?

C# 如何从内存中清除引用类型?,c#,.net,memory,heap-memory,C#,.net,Memory,Heap Memory,由于对象是引用类型,所以它们存储在堆中,而基本数据类型存储在堆栈上 但对象是基本数据类型和引用类型的集合,即对象可能有整数数据成员和/或其中可能有另一个对象 当作用域结束时,原始数据内存从堆栈中释放,但堆内存由垃圾收集器处理 现在我的问题是:如果一个对象也有一个原始数据成员,那么它们什么时候被删除?很难解释这样一个基本的问题,但理解起来并不总是那么容易。然而,在过去的15年里,许多很好的解释被写了出来 如果你不想阅读它们(显然…),这里有一个非常简短(因此不完整)的总结:(注:我仍然强烈建议在文

由于对象是引用类型,所以它们存储在堆中,而基本数据类型存储在堆栈上

但对象是基本数据类型和引用类型的集合,即对象可能有整数数据成员和/或其中可能有另一个对象

当作用域结束时,原始数据内存从堆栈中释放,但堆内存由垃圾收集器处理


现在我的问题是:如果一个对象也有一个原始数据成员,那么它们什么时候被删除?

很难解释这样一个基本的问题,但理解起来并不总是那么容易。然而,在过去的15年里,许多很好的解释被写了出来

如果你不想阅读它们(显然…),这里有一个非常简短(因此不完整)的总结:(注:我仍然强烈建议在文献中调查)

注:以下部分根据有关“基本类型”术语的评论对话进行了少量编辑:

(编辑) 在这个问题的上下文中,谈论“值类型”而不是“基元类型”更合适。无论类型是否为基元类型,在该上下文中唯一重要的是它是值类型还是引用类型。 (结束编辑)

现在重点是:

引用类型有一个引用(任何地方,如堆或堆栈中),它指向始终在堆上分配的实例。值类型存储在(堆或堆栈中的任何位置)立即嵌入该位置,因此没有间接寻址

样本:

  • 值类型的局部变量:堆栈
  • 引用类型的局部变量:实例本身在堆上,引用在堆栈上
  • 成员变量(值类型):嵌入实例的分配空间中,该实例是其成员变量
  • 成员变量(引用类型):其引用嵌入到实例的分配空间中,该实例是成员变量,其实例位于堆上
现在我的问题是:如果一个对象也有一个基本数据成员,那么它们什么时候被删除


答:当包含对象被移除时。(基于4个示例,希望这一点很清楚:包含对象可以在堆上或堆栈上,因此“包含对象删除”可以是GC集合或从方法返回时设置的简单堆栈指针。)

很难解释这样一个基本问题,但并不总是很容易理解。然而,在过去的15年里,许多很好的解释被写了出来

如果你不想阅读它们(显然…),这里有一个非常简短(因此不完整)的总结:(注:我仍然强烈建议在文献中调查)

注:以下部分根据有关“基本类型”术语的评论对话进行了少量编辑:

(编辑) 在这个问题的上下文中,谈论“值类型”而不是“基元类型”更合适。无论类型是否为基元类型,在该上下文中唯一重要的是它是值类型还是引用类型。 (结束编辑)

现在重点是:

引用类型有一个引用(任何地方,如堆或堆栈中),它指向始终在堆上分配的实例。值类型存储在(堆或堆栈中的任何位置)立即嵌入该位置,因此没有间接寻址

样本:

  • 值类型的局部变量:堆栈
  • 引用类型的局部变量:实例本身在堆上,引用在堆栈上
  • 成员变量(值类型):嵌入实例的分配空间中,该实例是其成员变量
  • 成员变量(引用类型):其引用嵌入到实例的分配空间中,该实例是成员变量,其实例位于堆上
现在我的问题是:如果一个对象也有一个基本数据成员,那么它们什么时候被删除

答:当包含对象被移除时。(基于4个示例,希望这一点很清楚:包含对象可以在堆上或堆栈上,因此“包含对象删除”可以是GC集合或从方法返回时设置的简单堆栈指针。)

由于对象是引用类型,所以它们存储在堆中,而基本数据类型存储在堆栈上

不完全是。值类型(包括原语,但也包括
struct
类型)是局部变量时存储在堆栈上。如果已装箱,它们也可以存储在堆上,或者存储在数组中,或者,正如您所注意的,存储在引用类型的字段中

引用类型有一个或多个引用,这些引用也可能存储在堆栈上,本地引用通过堆栈寻址,对象本身在堆上的表示形式也可以存储在堆栈上

当作用域结束时,原始数据内存从堆栈中释放,但堆内存由垃圾收集器处理

不完全是

首先,没有真正的“释放”操作。假设我们使用堆栈上的4个插槽来存储值1-4*:

[1][2][3][4][ ][ ][ ][ ]
          ^
       Using up to here.
(为了简单起见,我将完全忽略函数调用之间发生的事情)

现在我们停止使用最后两个插槽。没有必要“发布”任何内容:

[1][2][3][4][ ][ ][ ][ ]
    ^
  Using up to here.
[1][2][5][4][ ][ ][ ][ ]
       ^
     Using up to here.
仅当我们转到(例如,使用1个新插槽存储值5)时,我们需要覆盖任何内容:

[1][2][3][4][ ][ ][ ][ ]
    ^
  Using up to here.
[1][2][5][4][ ][ ][ ][ ]
       ^
     Using up to here.
“释放”只是改变了哪些内存被认为正在使用,哪些内存被认为可用

现在考虑下面的C代码:

假设您使用值
42
调用它。堆栈的相关部分是:

[42]
 ^
 Using up to here.
现在,在
int result=num+1之后范围中有两个值<代码>结果
num
。因此,堆栈可能是:

[42][43]
     ^
   Using up to here.
但是,
num
不再使用。编译器和jitter知道这一点,因此他们可能重复使用了相同的插槽:

[43]
 ^
 Using up to here.
因为“在scop中”
[Sync][RTTI][Field0][Field1]  … [FieldN]