欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

把Array说透(续一)

发布时间:2025/7/14 编程问答 62 豆豆
生活随笔 收集整理的这篇文章主要介绍了 把Array说透(续一) 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

1. 写在前面的

在前文中,我主要介绍了数组的一些相关知识,希望加深各位对Array的理解,不过,看过Ivony…同学的回复,我发觉自己离说透还有很大的距离,于是就有了下面的文章。在本文中,我也主要来围绕Ivony…同学提出的几点问题来作以说明,问题如下:

A、数组在托管堆内部是怎么存放的?数组元素的位置是连续的么?
B、非零基数组可以和零基数组转换么?
C、int[]与System.Array的关系到底是同一类型?还是基类与派生类的关系?
D、ldelem不检查下标越界么?
E、多维数组可以和零基数组转换么?
F、Array.Copy和CopyTo与手动拷贝性能有多大差距?
G、数组的协变是怎么做到的?
H、数组是如何实现泛型接口(如IList<T>)的?
I、多维数组每一维度长度必须相等么?必须零基么?
J、数组的Length属性到底指示的是什么?

2. 数组内存详解

在这里,我们依然把数组分为零基数组和非零基数组来讨论。

首先来看零基数组的内存分配,废话少说,我们先来看测试代码:

static unsafe void Main(string[] args) {int[] intArr = new int[3];intArr[0] = 1;intArr[1] = 2;intArr[2] = 3; }

代码本身很简单,接下来单步执行向下看,首先我们来查看一下源代码的汇编代码:

int[] intArr = new int[3]; 00000035 mov edx,3 0000003a mov ecx,61CD4192h 0000003f call FFFB2140 00000044 mov dword ptr [ebp-44h],eax 00000047 mov eax,dword ptr [ebp-44h] 0000004a mov dword ptr [ebp-40h],eax intArr[0] = 1; 0000004d mov eax,dword ptr [ebp-40h] 00000050 cmp dword ptr [eax+4],0 00000054 ja 0000005B 00000056 call 624B6B29 0000005b mov dword ptr [eax+8],1 intArr[1] = 2; 00000062 mov eax,dword ptr [ebp-40h] 00000065 cmp dword ptr [eax+4],1 00000069 ja 00000070 0000006b call 624B6B29 00000070 mov dword ptr [eax+0Ch],2 intArr[2] = 3; 00000077 mov eax,dword ptr [ebp-40h] 0000007a cmp dword ptr [eax+4],2 0000007e ja 00000085 00000080 call 624B6B29 00000085 mov dword ptr [eax+10h],3 }

在这里,我们就可以清晰地发现,在0x0000005b,0x00000070和0x00000085中,mov操作的目标地址之间是相隔4个Bytes的,也就是一个整数位。接下来我们来进一步证实。

当我们为数组分配过内存地址后,打开即使窗口查看数组所在的内存地址。

接下来打开内存窗口还查看0x015cc790内存块的数据:

以上是对数组赋值前的情况,赋值后的内存数据如下:

在这里可以更清晰地看出,数组元素之间差的正好是4个Bytes,也就是一个整数位。由此,我们可以得出结论。零基数组的元素在内存中是连续排布的。

接下来我们来看一下非零基数组:

由于空间所限,过程如上,就不再发,截图证明:

总之,当我们在托管堆中为数组分配内存时,数组占据一段连续的内存空间。

我们知道,当我们在托管堆中初始化一个对象时,每个对象都需要维护一个指针,该指针的作用是指向下一块空闲内存空间,由于对数组的操作经常是循环遍历等操作,这样如果把数组分配到一个连续的内存空间有一下两个好处:

A. 减少内存碎片

B. 节省内存,不需要维护指针

C. 基地址不需要发生变化,只需要改变偏移量即可,在一定程度上也提高了访问的效率。

接下来,我们还需要来补充一下数组在栈上分配内存的情况:

还记得上文中提到的这个关键字吧,stackalloc,就是他了。补充一下,在上文的回复中,有人问到说栈空间上分配的内存是不是也被垃圾回收器回收?这里的栈空间和C语言中的栈一样,没有垃圾回收器,每个变量都有他自己的作用域,当出了作用域后,变量自动销毁,具体的函数执行过程,请参看《深入理解计算机系统》。

3. 再论零基数组和非零基数组

我们先来看这样一段代码:

static void Main(string[] args) {int[,] intArr = (int[,])(Array.CreateInstance(typeof(Int32), new int[] { 3,4 }, new int[] { 1,1 }));intArr[2, 3] = 1; }

这段代码没有问题,我们将Array显式地转换成了强类型的二维数组,然后直接访问索引对其复制。

但是我们知道,对弱类型的Array而言,我们不能通过其下标访问他的元素,而只能通过SetValue和GetValue来获得值,但是我们看到SetValue和GetValue访问和设置的值的类型都是Object,这就意味着我们需要对其进行一次装箱或者拆箱。那么我们有没有办法也生成一个强类型的非零基数组呢?

在上文中,我们提到过,.NET Framework的几种数组类型:

一维零基数组:System.Int32[]。一维非零基数组:System.Int32[*]。多维数组:System.Int32[,]。

那么也就是说,我们是否能通过这样的代码来把Array转换成一维非零基数组呢?

static void Main(string[] args) {int[*] intArr = (int[*])(Array.CreateInstance(typeof(Int32), new int[] { 3}, new int[] { 1 })); }

事实证明是错误的。在CLR via C#中Jeffery有这样一段话:

“C# does not allow you to declare a variable of type string[*],and therefore it is not possible to user C# syntax to access a single-dimensional ,non-zero-based array.”

这段话翻译成中文的意思就是:C#不允许声明一个string[*]类型的变量,因此,我们能够使用C#语法来访问一个非零基一维数组。

通过以上的解释,我们也许又额外明白了一点,Array究竟是数组类型,还是数组类型的基类?

通过上面的一些代码,我们不妨又把数组重新分类为“强类型数组”和“弱类型数组”。而Array就属于弱类型数组。为什么Array是所有数组类型的基类,我没想出办法来如何证明,只是看到Jeffery说了这样一句话:

“All Arrays are Implicitly Derived from System.Array”。

我想这句话可以说明问题了,不过还是希望各位大侠指点如果证明这一点。

未完持续…………

 

转载于:https://www.cnblogs.com/kym/archive/2009/10/09/1579958.html

总结

以上是生活随笔为你收集整理的把Array说透(续一)的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。