C語言中文網 目錄

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

在程序設計中,為了便于程序處理,通常把具有相同類型的若干變量按有序的形式組織在一起,這些按序排列的同類數據元素的集合稱為數組。其中,集合中的每一個元素都相當于一個與數組同類型的變量;集合中的每一個元素用同一個名字和它在集合中的序號(下標)來區分引用。來看下面一個數組定義:
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 開始。
  • 數組在內存中占據連續的字節單元。
  • 數組占據的字節單元數等于數組元素個數乘以該數組所屬數據類型的數據占據的字節單元數(元素個數乘以元素類型大小)。
  • 數組元素按順序連續存放。

為了讓大家更加清楚地看到數組的存儲結構,繼續看下面的示例代碼:
int a[5];
printf("sizeof(a):%d\n",sizeof(a));
printf("sizeof(a[0]):%d\n",sizeof(a[0]));
printf("sizeof(a[5]):%d\n",sizeof(a[5]));
printf("sizeof(&a):%d\n",sizeof(&a));
printf("sizeof(&a[0]):%d\n",sizeof(&a[0]));
printf("-----------------------------------\n");
printf("&a:%d\n",&a);
printf("&a[0]:%d\n",&a[0]);
printf("&a[1]:%d\n",&a[1]);
printf("&a[2]:%d\n",&a[2]);
printf("&a[3]:%d\n",&a[3]);
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:
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 個元素組成。

結合上面的分析來看下面的示例代碼:
int main(void)
{
    int a[5][4];
    int i=0;
    int j=0;
    printf("sizeof(a):%d\n",sizeof(a));
    printf("sizeof(a[0]):%d\n",sizeof(a[0]));
    printf("sizeof(a[0][0]):%d\n",sizeof(a[0][0]));
    printf("-----------------------------------\n");
    printf("sizeof(&a):%d\n",sizeof(&a));
    printf("sizeof(&a[0]):%d\n",sizeof(&a[0]));
    printf("sizeof(&a[0][0]):%d\n",sizeof(&a[0][0]));
    printf("-----------------------------------\n");
    printf("&a:%d\n",&a);
    printf("&a[0]:%d\n",&a[0]);
    printf("&a[0][0]:%d\n",&a[0][0]);
    printf("-----------------------------------\n");
    for(i=0;i<5;i++)
    {
        printf("&a[%d]:%d\n",i,&a[i]);
        for(j=0;j<4;j++)
        {
            printf("&a[%d][%d]:%d\n",i,j,&a[i][j]);
        }
    }
    return 0;
}
在上面的示例代碼中,由于數組名代表的是數組首元素的首地址,因此下面的三行代碼的輸出結果都是相同的:
printf("&a:%d\n",&a);
printf("&a[0]:%d\n",&a[0]);
printf("&a[0][0]:%d\n",&a[0][0]);
同時,當將 a[0] 作為一個數組名稱時,該數組的首地址也就保存在 a[0] 中(這里 a[0] 作為一個整體看作數組名,而不是一個數組的元素)。因此,不用取地址運算符 &,直接輸出 a[0] 的值也可得到數組的首地址,即下面的兩行代碼輸出的結果是等價的:
printf("&a[0]:%d\n",&a[0]);
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 作為左值。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo