歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

數組指針與二維數組的尋址

數組指針與二維數組的尋址,引例:已知如下程序

1 #include <stdio.h>
2 main()
3 { 
4     int x[3][4] = {1,3,5,7,9,11,2,4,6,8,10,12} ;
5     int (*p)[4] = x, k = 1, m, n = 0;
6     for(m=0; m < 2; m++) 
7         n += *(*(p+m)+k);
8     printf("%d\n",n);
9 }

試寫出程序的輸出值。(雖然我很討厭做這種筆頭功夫的題,我也堅信編程語言是在實踐中練出來的,但是這個題還是比較經典,所以還是拿來當一個例子來說明一下數組指針到底是個什麼玩意)

最初在學習C語言時,就一直為這兩個名詞所困擾。其實也怪漢語的博大精深,兩個詞交換一下位置,所表示的含義就不一樣了。如果直接從英文來說,指針數組叫做Array of pointers,明顯重點是array,至於是什麼樣的array呢,就是存放pointers的array。而數組指針叫做pointer of an array,重點是pointer,那麼這個pointer 指向的是什麼呢,是一個array。當然這個指向的array到底是什麼樣的,還需要方括號的維度說明,以及前面的類型說明。

接著回到剛才的引例,x為定義的一個二維數組,p是一個數組指針,指向一個長度為4的數組,一開始指向x的第一行(x的行是一個長度為4的int型數組)接下來一個for循環,依次對p+1取值,加上k(實際就是1)後再取值,並將其累加到變量n上。循環一共執行了2次,分別取第1行和第2行(對應第一個下角標0和1)的第一個元素(也就是x[0][1],x[1][1]),因此最後的輸出結果是3+11=14.

光從紙面上分析顯然是不夠的。GCC編譯器對上述程序產生如下的代碼

 1 0x401340    push   %ebp
 2 0x401341    mov    %esp,%ebp
 3 0x401343    and    $0xfffffff0,%esp
 4 0x401346    sub    $0x50,%esp
 5 0x401349    call   0x4019d0 <__main>
 6 0x40134e    movl   $0x1,0x10(%esp)
 7 0x401356    movl   $0x3,0x14(%esp)
 8 0x40135e    movl   $0x5,0x18(%esp)
 9 0x401366    movl   $0x7,0x1c(%esp)
10 0x40136e    movl   $0x9,0x20(%esp)
11 0x401376    movl   $0xb,0x24(%esp)
12 0x40137e    movl   $0x2,0x28(%esp)
13 0x401386    movl   $0x4,0x2c(%esp)
14 0x40138e    movl   $0x6,0x30(%esp)
15 0x401396    movl   $0x8,0x34(%esp)
16 0x40139e    movl   $0xa,0x38(%esp)
17 0x4013a6    movl   $0xc,0x3c(%esp)
18 0x4013ae    lea    0x10(%esp),%eax
19 0x4013b2    mov    %eax,0x44(%esp)
20 0x4013b6    movl   $0x1,0x40(%esp)
21 0x4013be    movl   $0x0,0x48(%esp)
22 0x4013c6    movl   $0x0,0x4c(%esp)
23 0x4013ce    jmp    0x4013f9 <main+185>
24 0x4013d0    mov    0x4c(%esp),%eax
25 0x4013d4    lea    0x0(,%eax,4),%edx
26 0x4013db    mov    0x40(%esp),%eax
27 0x4013df    add    %edx,%eax
28 0x4013e1    lea    0x0(,%eax,4),%edx
29 0x4013e8    mov    0x44(%esp),%eax
30 0x4013ec    add    %edx,%eax
31 0x4013ee    mov    (%eax),%eax
32 0x4013f0    add    %eax,0x48(%esp)
33 0x4013f4    addl   $0x1,0x4c(%esp)
34 0x4013f9    cmpl   $0x1,0x4c(%esp)
35 0x4013fe    jle    0x4013d0 <main+144>
36 0x401400    mov    0x48(%esp),%eax
37 0x401404    mov    %eax,0x4(%esp)
38 0x401408    movl   $0x403024,(%esp)
39 0x40140f    call   0x401c40 <printf>
40 0x401414    leave
41 0x401415    ret

其中第4行編譯器為局部變量(auto)在棧上分配內存空間0x50字節,6~17行,編譯器為二維數組x初始化,其中,x[0][0]的地址為%esp+10。19~22行分別為p,k,m,n初始化。(從中可以看出,p初始化使用了leal指令取第一個元素的地址,且p只占用了4個字節,也就是說,從數據大小來看,數組指針本質上還是一個指針)

現在想要研究編譯器如何對數組指針進行操作,通過jle指令可以定位到循環為24~35行。在原始的C語言代碼中,for循環的body-statement只有一句復合語句,最後的操作顯然對應累加,也就是32行的add指令(33行的addl顯然是計數器累加,因為34行用到了cmpl指令判斷大小)。32行的add指令中,%esp+48對應變量n,31行用%eax的值作為地址進行尋址,將地址為%eax的值放進%eax中,顯然對應C語言語句中最外層的一個*號。30行的add指令後的%eax的值顯然便是表達式:*(p+m)+k的值。

重點在於理解編譯器如何解析這個表達式了。24行取%esp+0x4c(m的值),25行用leal指令將m*4並放入%edx寄存器中,26行取%esp+0x40(k的值)放入寄存器%eax中,27行將%eax和%edx的值相加,得到整個的偏移地址4m+k,28行將整個偏移地址乘以4得到實際的字節偏移地址,29行再將其與數組第一個元素的地址相加,得到表達式*(p+m)+k的值了。因此,25行leal指令得到的系數4,恰好對應定義的數組指針的長度4。如果在原題中將(*p)[4]改為(*p)[3],於是編譯器得到如下代碼(僅截取循環內):

 1 0x4013d0    mov    0x4c(%esp),%edx
 2 0x4013d4    mov    %edx,%eax
 3 0x4013d6    add    %eax,%eax
 4 0x4013d8    add    %eax,%edx
 5 0x4013da    mov    0x40(%esp),%eax
 6 0x4013de    add    %edx,%eax
 7 0x4013e0    lea    0x0(,%eax,4),%edx
 8 0x4013e7    mov    0x44(%esp),%eax
 9 0x4013eb    add    %edx,%eax
10 0x4013ed    mov    (%eax),%eax
11 0x4013ef    add    %eax,0x48(%esp)
12 0x4013f3    addl   $0x1,0x4c(%esp)
13 0x4013f8    cmpl   $0x1,0x4c(%esp)
14 0x4013fd    jle    0x4013d0 <main+144>

這裡編譯器使用兩條add指令計算數組長度3代替了原先的leal指令計算的數組長度4(編譯器往往會選擇合適的指令來減小開銷,比如用移位和加法指令代替常數乘法,但是會使得匯編碼和C代碼的對應不是很明顯),而後的代碼與原先如出一轍。

可以看出,數組指針指向的是一個數組,數組指針進行自增,會將實際的地址指向下一個依靠的數組。由於二維數組在內存中實際也是按照“行優先”的規則映射到一維的線性的數組中來存儲的,編譯器在解釋數組指針的過程中,會首先計算數組指針所指向的數組的長度(定義數組指針時確定),然後根據所指向的數組的長度計算偏移地址,將其與初始化的基地址(將其與一個二級指針關聯時得到的基地址)相加,得到所指向的數組的第一個元素的地址。因此,數組指針的長度和與它相關聯的實際的二維數組的行列長度並不需要嚴格一致,只是為了使用方便,往往會將數組指針所指向的數組的長度與實際需要操作的二維數組的行長度相對應。
事實上,訪問二維數組D(定義為ElementType D[R][C])中的i行j列的元素時,通用的尋址方法是

&D[i][j]=xD+L(C·i+j),其中xD為二維數組的首地址,L為數組的元素數據類型的大小,C為定義的行長度。

數組指針的尋址本質上是一致的。在開頭的例題裡,公式中xD=p,i=m,j=k。

 參考:深入理解計算機系統第二版,p158.3.8節 數組的分配與訪問。

深入理解計算機系統(原書第2版) PDF清晰中文版  下載 http://www.linuxidc.com/Linux/2015-03/114720.htm

Copyright © Linux教程網 All Rights Reserved