把Array說透(續一)

來源:互聯網
上載者:User

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”。

我想這句話可以說明問題了,不過還是希望各位大俠指點如果證明這一點。

未完持續…………

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.