數組的存儲,C語言數組的存儲實質詳解

在程序設計中,爲了便於程序處理,通常把具有相同類型的若干變量按有序的形式組織在一起,這些按序排列的同類數據元素的集合稱爲數組。其中,集合中的每一個元素都相當於一個與數組同類型的變量;集合中的每一個元素用同一個名字和它在集合中的序號(下標)來區分引用。來看下面一個數組定義:


 
  1. int a[5];

如圖 1 所示,當定義一個數組a時,編譯器根據指定的元素個數和元素的類型分配確定大小(元素類型大小×元素個數)的一塊內存,並把這塊內存的名字命名爲 a,名字 a 一旦與這塊內存匹配就不能再改變。其中,a[0]、a[1]、a[2]、a[3] 與 a[4] 都爲 a 的元素,但並非元素的名字(數組的每一個元素都是沒有名字的)。



圖 1 int[5]的存儲結構


在 32 位系統中,由於 int 類型的數據佔 4 字節單元,因此該數組 a 在內存中共佔據連續的 4×5=20 字節單元,依次保存 a[0]、a[1]、a[2]、a[3] 與 a[4] 共 5 個元素。如果這裏假設元素 a[0] 的地址是 10000,則元素 a[1] 的地址是 10000+1×4=10004; 元素 a[2] 的地址是 10000+2×4=10008; 元素 a[3] 的地址是 10000+3×4=10012; 元素 a[4] 的地址是 10000+4×4=10016。

由此可見,數組的存儲具有如下特點:

  • 索引從 0 開始。
  • 數組在內存中佔據連續的字節單元。
  • 數組佔據的字節單元數等於數組元素個數乘以該數組所屬數據類型的數據佔據的字節單元數(元素個數乘以元素類型大小)。
  • 數組元素按順序連續存放。


爲了讓大家更加清楚地看到數組的存儲結構,繼續看下面的示例代碼:


 
  1. int a[5];
  2. printf("sizeof(a):%d\n",sizeof(a));
  3. printf("sizeof(a[0]):%d\n",sizeof(a[0]));
  4. printf("sizeof(a[5]):%d\n",sizeof(a[5]));
  5. printf("sizeof(&a):%d\n",sizeof(&a));
  6. printf("sizeof(&a[0]):%d\n",sizeof(&a[0]));
  7. printf("-----------------------------------\n");
  8. printf("&a:%d\n",&a);
  9. printf("&a[0]:%d\n",&a[0]);
  10. printf("&a[1]:%d\n",&a[1]);
  11. printf("&a[2]:%d\n",&a[2]);
  12. printf("&a[3]:%d\n",&a[3]);
  13. printf("&a[4]:%d\n",&a[4]);

對於上面的示例代碼,在 32 位系統中:

  • 對於sizeof(a),sizeof(a)=sizeof(int)×5=4×5=20。
  • 對於sizeof(a[0]),sizeof(a[0])=sizeof(int)=4。
  • 對於sizeof(a[5]),sizeof(a[0])=sizeof(int)=4。


這裏需要說明的是,因爲 sizeof 是關鍵字,而不是函數(函數求值是在運行的時候,而關鍵字 sizeof 求值是在編譯的時候),因此,雖然並不存在 a[5] 這個元素,但是這裏也並沒有真正訪問 a[5],而是僅僅根據數組元素的類型來確定其值。所以這裏使用 a[5] 並不會出錯,sizeof(a[5]) 的結果爲 4。

對於 &a[0],它表示取數組首元素 a[0] 的首地址;而對於 &a,表示取數組 a 的首地址。因此,&a[0] 的值與 &a 的值相同,sizeof(&a[0]) 與 sizeof(&a) 在 32 位系統下的結果都爲 4。

因此,運行上面的示例代碼,運行結果爲:
sizeof(a):20
sizeof(a[0]):4
sizeof(a[5]):4
sizeof(&a):4
sizeof(&a[0]):4
-----------------------------------
&a:6356732
&a[0]:6356732
&a[1]:6356736
&a[2]:6356740
&a[3]:6356744
&a[4]:6356748

到現在爲止,相信大家已經基本瞭解了一維數組的存儲結構。或許這個時候你會問,那麼二維數組及多維數組又是怎樣存儲的呢?其實,其原理與一維數組一樣。下面,我們來定義一個 5 行 4 列的二維數組 a:


 
  1. int a[5][4];

對於二維數組,它在邏輯上是由行和列組成的。因此,我們可以將上面的二維數組 a 分爲三層來理解,如圖 2 所示。



圖 2


在圖 2 中:
在第一層,將數組 a 看作一個變量,該變量的地址爲 &a,長度爲 sizeof(a)。因爲數組的長度爲元素數量乘以每個元素類型的大小,這裏的二維數組 a 爲 5 行 4 列共 20 個元素,每個元素佔用 4 字節,所以變量 a 佔用 80 字節。

在第二層,將數組 a 看作一個一維數組,由 a[0]、a[1]、a[2]、a[3] 與 a[4] 等 5 個元素組成。數組的首地址爲 a 或 &a[0](即數組首地址和第一個元素的地址相同,而每個數組元素的地址相差爲 16,表示每個數組元素的長度爲 16),使用 sizeof(a[0]) 可得到數組元素的長度。

在第三層,將第二層中的每個數組元素看作一個單獨的數組。第二層中的每一個元素又由 4 個元素構成,如 a[0] 又由 a[0][0]、a[0][1]、a[0][2] 與 a[0][3] 等 4 個元素組成。

結合上面的分析來看下面的示例代碼:


 
  1. int main(void)
  2. {
  3. int a[5][4];
  4. int i=0;
  5. int j=0;
  6. printf("sizeof(a):%d\n",sizeof(a));
  7. printf("sizeof(a[0]):%d\n",sizeof(a[0]));
  8. printf("sizeof(a[0][0]):%d\n",sizeof(a[0][0]));
  9. printf("-----------------------------------\n");
  10. printf("sizeof(&a):%d\n",sizeof(&a));
  11. printf("sizeof(&a[0]):%d\n",sizeof(&a[0]));
  12. printf("sizeof(&a[0][0]):%d\n",sizeof(&a[0][0]));
  13. printf("-----------------------------------\n");
  14. printf("&a:%d\n",&a);
  15. printf("&a[0]:%d\n",&a[0]);
  16. printf("&a[0][0]:%d\n",&a[0][0]);
  17. printf("-----------------------------------\n");
  18. for(i=0;i<5;i++)
  19. {
  20. printf("&a[%d]:%d\n",i,&a[i]);
  21. for(j=0;j<4;j++)
  22. {
  23. printf("&a[%d][%d]:%d\n",i,j,&a[i][j]);
  24. }
  25. }
  26. return 0;
  27. }

在上面的示例代碼中,由於數組名代表的是數組首元素的首地址,因此下面的三行代碼的輸出結果都是相同的:


 
  1. printf("&a:%d\n",&a);
  2. printf("&a[0]:%d\n",&a[0]);
  3. printf("&a[0][0]:%d\n",&a[0][0]);

同時,當將 a[0] 作爲一個數組名稱時,該數組的首地址也就保存在 a[0] 中(這裏 a[0] 作爲一個整體看作數組名,而不是一個數組的元素)。因此,不用取地址運算符 &,直接輸出 a[0] 的值也可得到數組的首地址,即下面的兩行代碼輸出的結果是等價的:


 
  1. printf("&a[0]:%d\n",&a[0]);
  2. printf("&a[0]:%d\n",a[0]);

運行上面的示例代碼,運行結果爲:
sizeof(a):80
sizeof(a[0]):16
sizeof(a[0][0]):4
-----------------------------------
sizeof(&a):4
sizeof(&a[0]):4
sizeof(&a[0][0]):4
-----------------------------------
&a:6356664
&a[0]:6356664
&a[0][0]:6356664
-----------------------------------
&a[0]:6356664
&a[0][0]:6356664
&a[0][1]:6356668
&a[0][2]:6356672
&a[0][3]:6356676
&a[1]:6356680
&a[1][0]:6356680
&a[1][1]:6356684
&a[1][2]:6356688
&a[1][3]:6356692
&a[2]:6356696
&a[2][0]:6356696
&a[2][1]:6356700
&a[2][2]:6356704
&a[2][3]:6356708
&a[3]:6356712
&a[3][0]:6356712
&a[3][1]:6356716
&a[3][2]:6356720
&a[3][3]:6356724
&a[4]:6356728
&a[4][0]:6356728
&a[4][1]:6356732
&a[4][2]:6356736
&a[4][3]:6356740

理解 &a[0] 和 &a 的區別

在對上面的數組示例分析的過程中,可以發現 &a[0] 和 &a 的值是相同的。但是要注意,儘管它們的結果相同,但其所表達的意義卻完全不相同,這一點一定要注意。

因爲數組名包含數組的首地址(即數組第一個元素的地址),或者說數組名指向數組的首地址(或第一個元素),所以,對於 &a,表示取數組 a 的首地址;而對於 &a[0],它表示取數組首元素 a[0] 的首地址。這就好像陝西的省政府在西安,而西安的市政府同樣也在西安。雖然兩個政府機構都在西安,但其代表的意義完全不同。

理解數組名 a 作爲右值和左值的區別

當數組名 a 作爲右值的時候,其意義與 &a[0] 是一樣的,代表的是數組首元素的首地址(注意,不是數組的首地址,這一點一定要區分開)。但是,這僅僅只是代表,編譯器並沒有爲其分配一塊內存空間來存放其地址,這一點就與指針有很大的差別。

同理,當數組名 a 作爲左值的時候,代表的同樣是數組的首元素的首地址。但是,這個地址開始的一塊內存是一個總體(即數組一旦定義就會被分配一片連續的存儲空間)。因此,我們只能訪問數組的某個元素,而無法把數組當一個總體進行訪問。也就是說我們可以將 a[0] 作爲左值,而無法將 a 作爲左值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章