泛型内存管理

本文关键字:管理 内存 泛型 | 更新日期: 2023-09-27 18:19:16

我有一个关于如何管理强类型泛型内存的问题

List<int> ints1 = new List<int>();
ints1.Add(1); ints1.Add(2); ints1.Add(3);
int[] ints2 = new int[10]();
ints2.Add(6); ints2.Add(7); ints2.Add(8);

问题1:我假设ints1是用一个新的关键字(new List<int>())初始化的,它成为一个引用类型。值1,2,3存储在内存中的位置(它们存储在堆栈中还是堆中)?

问题2:我知道List<int>int[]之间的一个东西,List<int>可以在运行时将其大小缩放到任何大小,这在int[]中在编译时受到限制。所以,如果值1,2,3被存储在堆栈中,如果一个新的元素被添加到List<int>中,比如4,它不会连续到前3位,那么ints1如何知道4的内存位置?

泛型内存管理

我假设ints1是用一个新的关键字new List<int>()初始化的,它变成了一个引用类型

这个假设是不正确的。你也可以在值类型上使用"new"关键字!

int x = new int();

使用"new"不会使任何东西成为引用类型。您可以对引用类型或值类型使用"new"。"new"表示将分配存储空间,并且将调用构造函数

在值类型上使用"new"的情况下,分配的存储空间是临时存储空间。将对临时存储的引用传递给构造函数,然后将初始化后的结果复制到最终目的地(如果有的话)。("new"通常与赋值操作一起使用,但不一定)

在引用类型的情况下,存储分配两次:为实例分配长期存储,为对实例的长期存储的引用分配短期存储。引用被传递给构造函数,构造函数初始化长期存储。然后将引用从短期存储复制到最终目标(如果有的话)。

使List<int>成为引用类型的原因是List<T>被声明为类。

值1,2,3存储在内存中的位置(它们存储在堆栈中还是堆中)?

我们努力做一个内存管理器,让你不关心东西存储在哪里。值存储在短期内存池(作为堆栈或寄存器实现)或长期内存池(作为垃圾收集堆实现)中。根据该值的已知生存期分配存储。如果已知该值是短期值,则将其存储分配到短期池中。如果的值不是已知的短期值,则必须将其分配给长期池。

列表所拥有的1,2,3可以永远存在;我们不知道该列表是否会在当前激活框架之后继续存在。因此,存储1,2,3的内存被分配到长期池中。

不要相信"值类型总是在堆栈上分配"的谎言。显然,这是不可能的,因为包含数字的类或数组无法在当前堆栈帧中存活!值类型在池中分配,这对于它们已知的生命周期是有意义的。

List<int>可以在运行时将其大小缩放为任何大小,而int[]

正确的。看看List<T>是如何做到这一点的,这很有教育意义。它只是分配一个大于所需的数组。如果它发现它猜的太小,它会分配一个新的、更大的数组,并将旧数组的内容复制到新的数组中。List<T>只是一堆数组副本的方便包装器!

如果值1,2,3被存储在堆栈中,并且一个新的元素4被添加到列表中,那么它将不连续到前三个。

正确的。这就是为什么值1,2,3的存储空间没有在堆栈上分配的原因之一。存储空间实际上是在堆上分配的一个数组。

那么列表如何知道第4项的内存位置呢?

list分配的数组太大。当您添加新项时,它会将其粘贴到过大的数组中未使用的空间中。当数组的空间用完时,它分配一个新的数组。

"new"语法用于初始化值类型和引用类型。新的列表在堆上创建;值在堆栈上加载(即在它们被添加到列表之前),但是一旦添加,它们就在堆上,在支撑列表的int[]中。数组总是在堆上。

它们被复制到数组的事实也回答了第2部分的问题。数组大小过大,只有在满的时候才会重新分配。

注意;List<int>不会"变成"引用类型;总是是引用类型

泛型(泛型集合)的内存管理与非泛型类型完全相同。

你的ints1列表使用了一个数组。所以它是相同的ints2(当它已被纠正)。在这两种情况下,堆上的一块内存都保存着int的值。

List<>类由一个数组、一个int Count和一个int Capacity属性组成。当你Add()一个元素Count是递增的,当它传递给Capacity时,一个新的数组被分配并且内容被复制。

问题1:http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx说:

List类是ArrayList类的泛型等价物。它使用大小为的数组实现IList泛型接口根据需要动态增加。

这看起来像一个简单的数组,它只是在溢出时重新分配。在每次重新分配时,大小都会翻倍——我研究过一次,但不记得是为了什么。

数组在托管堆上分配,就像您刚刚声明它一样。

  1. List是一个引用类型,不管你怎么看它。所有这些类型都在堆上分配。我不知道c#编译器是否足够聪明,能够弄清楚在方法之外不使用的对象可以在堆栈上分配,(Eric Lippert可能会告诉我们,)但即使它做到了,这也是你作为程序员不需要担心的事情。这只是编译器为你做的一个优化,而你却没有注意到。

  2. int类型的数组也是一个引用类型,它也是在堆上分配的,就这么简单。没有必要担心堆栈中存在一些假设的数组碎片,因为它们根本没有在堆栈中分配。

List<int> ints1 = new List<int>();

我假定ints1是用new关键字(new List())初始化的,它成为一个引用类型。

让我们先弄清楚一些术语:

  • ints1不是任何类型的类型,而是变量,因此它永远不能"成为引用类型"。
  • 变量具有静态("编译时")类型。例如,ints1被声明为List<int>类型。变量的静态类型永远不会改变。
  • 变量引用的值或对象的类型称为动态("运行时")类型。完成上述赋值后,ints1的动态类型为List<int>。当一个新值或对象被赋值时,变量的动态类型可能会改变。

ints1的情况下,这两种类型恰好是相等的,但并不总是这样:

ICollection<int> ints3 = new List<int>();

这里,静态类型是ICollection<int>(总是),动态类型是List<int>(赋值之后)。


问题1的答案:

ints1.Add(1); ints1.Add(2); ints1.Add(3);

值1,2,3存储在内存中的位置(它们存储在堆栈中还是堆中)?

官方的答案是,这应该被认为是List<T>的实现细节,不应该影响你。您只需要知道可以在List<T>(如AddClear等)上执行的操作及其特性(如前置/后设条件、性能等)。

如果你仍然想知道这个类型是如何在内部工作的,让我们从这个提示开始:

List<T>类[…]使用数组实现IList<T>泛型接口,该数组的大小根据需要动态增加。本;MSDN参考页面List<T>部分备注

意思是,您可以将List<T>想象为T[]类型的数组,它在需要时增加其容量。在ints1的情况下,考虑int[]。因为int是一个值类型,所以列表中的具体值(1,2,3)将被就地存储。如果它是引用类型,则每个值将存储在单独的位置,并且数组将只包含引用。

请注意,我在上面的段落中没有提到术语"堆栈",也没有提到"堆"。这是因为这些概念是另一个实现细节,即。net平台的一个,您不应该太关心。(参见Eric Lippert的博客系列文章,"堆栈是一个实现细节",以及我对之前问题的回答,"在c++/c#/Java中,new总是在堆上分配吗?")


问题2的答案:

List可以在运行时将其大小缩放到任意大小,这在编译时受到int[]的限制。那么,如果值1 2 3存储在堆栈中,如果一个新的元素被添加到List中,比如说4,它不会连续到前3位,那么ints1如何知道4的内存位置呢?

同样,你不需要考虑这个。关于List<int>,重要的是它提供的特定特性和操作是否符合您的目的。但如果我就此打住,我就不会回答你的问题了,所以让我们简单地思考一下:

当你谈论"堆栈"时,你可能是指正在执行的线程的调用堆栈。据我所知,使用这种数据结构的虚拟机和编程语言大多将其用于向函数/方法传递参数,也可能用于存储函数的局部值。这与说调用堆栈也用于局部变量不同,因为局部变量可能包含一个对象,该对象通过返回值或通过ref/out参数等返回给调用函数。

对我来说,在正常情况下,List<int>对象似乎不太可能在调用堆栈上结束(顺便说一句,我不考虑stackalloc或与unsafe上下文相关的任何其他内容)。请记住,List<T>是一个引用类型:虽然对实际对象的引用有可能(而且很有可能)在调用堆栈上结束,但对象的数据本身很可能不会。

IList<T>的一个(可能是naïve)实现使用固定大小的数组进行项存储,当需要增加其容量时,可以动态分配一个新的更大的数组,然后将当前数组的内容复制到新数组中,然后放弃旧数组,转而使用新数组。

问题1

c#中的

列表内部包含数组。List引用堆上的一个位置,该位置是一个存储所有值的数组。所以这些值存储在堆上。数组也是一样,如果它是类的一部分。

问题2

堆栈是连续的,因此在堆栈上压入另一个int将意味着它的内存地址是前一个int + 4的位置。当您添加项时,列表的工作方式是创建一个比您需要的更大的数组。当你达到数组的长度时,有一个算法会创建一个更大的数组,并复制当前的值。

另一个你可能感兴趣的东西是链表。链表内部不处理数组,而是处理节点。每个节点包含数据和列表中下一个节点的位置。双链表包含所有节点,以及列表中前一个节点的位置。