5

假设我有一些struct像这样的简单:

public struct WeightedInt {
    public int value;
    public double weight;
}

然后假设我有一个这个结构的实例集合:

List<WeightedInt> weightedInts = new List<WeightedInt>();

据我了解值类型与引用类型,值类型是在堆栈上分配的,因此一旦实例化所述对象的函数终止,值类型对象就会从内存中清除。这意味着在以下代码中:

void AddWeightedIntToList(int value, double weight) {
    WeightedInt wint = new WeightedInt();
    wint.value = value;
    wint.weight = weight;

    weightedInts.Add(wint);
}

添加局部变量的副本,而局部变量本身在完成后从内存中删除。wintweightedIntsAddWeightedIntToList

首先:这是正确的吗?

其次,这个副本wint存储在哪里?它不能在堆栈上,因为一旦函数完成它就会消失(对吗?)。这是否意味着副本与 一起存储在堆上weightedInts?并且它是在被删除后被垃圾收集的,就好像它是引用类型的实例一样?

这个问题当然有可能在某处的文章中得到回答,在这种情况下,指向该文章的链接将是一个完全可以接受的答案。我只是没有运气找到它。

4

2 回答 2

7

首先:这是正确的吗?

是的。一旦范围结束,原件就“消失”了。

其次,这个 wint 的副本存储在哪里?它不能在堆栈上,因为一旦函数完成它就会消失(对吗?)。这是否意味着副本与 weightedInts 一起存储在堆上?并且它是在被删除后被垃圾收集的,就好像它是引用类型的实例一样?

您的实例List<WeightedInt>在堆上创建一个数组。当您将该数组的一部分“添加”到列表中时,您正在为该数组的一部分分配您的值类型的副本。该值保存在堆上,作为数组的一部分(在 List 类内部)。

当您的 weightedInts 成员超出范围时,它将变得无根,并且有资格被垃圾收集。在那之后的某个时刻,GC 将运行,并释放与其内部数组关联的内存,从而释放与您的 wint 副本关联的内存。


编辑:

此外,当您致电时:

weightedInts.Remove(wint);

发生了一些事情(用List<T>)。

首先,列表找到等于 wint 的值类型的 FIRST 实例的索引。然后它调用 RemoteAt(index)。

RemoveAt(index) 方法基本上将内部大小标记为小一,然后检查您要删除的索引。如果它在列表的中间,它实际上使用 Array.Copy 将所有值类型实例复制到一个元素上,以“缩小”列表。然后它将数组末尾的内存清零。

数组本身不会缩小,因此删除元素不会释放内存。如果你想回收这个内存(甚至让它有资格被 GC 释放),你需要调用List<T>. 修剪过量()。

于 2009-10-05T19:20:15.300 回答
1

值类型总是在堆栈上分配是一个常见的误解。

您刚刚展示的示例是在堆上分配值类型的完美示例。

于 2009-10-05T19:22:56.663 回答