上一篇《語義陷阱-數組和指針》
聊過數組和指針的區別,主要是對於數組和指針在內存中的訪問方式加以區分,這篇博文則從更深層的角度剖析數組和指針的聯系
如果你也對底層感興趣、我向這篇文章會對你有所幫助,
什麼時候數組和指針相同(When an Array Is a Pointer )
在實際應用中,他們可以互換的情形要大大多於不能互換的情形。首先再回顧一下聲明和定義,(上一篇中有提到這裡在深入一下)
聲明本身還可以進一步分為三種情況:
1)外部數組的聲明(external array)
2)數組的定義(它是聲明的一種特殊情況,它分配內存空間,並可能提供一個初值)
3)函數參數的聲明
所有作為函數參數的聲明在編譯時總是會轉換為指針(指向數組第一個元素),而其他情況的聲明,數組就是數組 ,指針就是指針
可以用如下的圖來說明他們的關系:
稍微總結一下:
對於編譯過程中數組會轉化為指針的情況,數組和指針可以互換,比如:聲明為函數參數的時候 fun(int a [])和fun(int *a )是等同的。因為編譯的過程中fun(int a [])
中的數組會轉化為指針形式,也就和fun(int *a )的效果一樣了。
如果編譯到時候數組不被當做指針處理,那麼聲明的時候不能等同。對於這種情況,數組和指針是不一樣的的,在運行時的表示形式也不一樣,並可能產生不同的代碼。
對編譯器而言,一個數組就是一個地址,一個指針就是地址的地址。
c語言標准對此做了如下說明:
1)An array name in an expression (in contrast with a declaration) is treated by the compiler as a pointer to the first element of the array
(paraphrase, ANSI C Standard, paragraph 6.2.2.1).
2)A subscript is always equivalent to an offset from a pointer (paraphrase,
ANSI C Standard, paragraph 6.3.2.1).
3)An array name in the declaration of a function parameter is treated by
the compiler as a pointer to the first element of the array (paraphrase, ANSI
C Standard, paragraph 6.7.1).
即:
1)表達式中的數組名(與聲明不同)被編譯器當做一個指向該數組第一個元素的指針
2)下標總是與偏移量相同
3)在函數參數的聲明中,數組名被編譯器當做指向該數組第一個元素的指針
我覺得有必要對上文中出現的“表達式”做一個解釋
int arry[10]={,,,,,};
int a=arry[2];
那麼第二句中的int a=arry[2];就是所謂的表達式中出現的數組名了,這個時候編譯器會把數組名arry當做指向數組第一個元素的指針,也就是arry[0]的地址
下標總是與偏移量相同,也就是arry[2]中的下標2和arry[2]這個元素在內存中相對於第一個元素的偏移量也是2它們是相同的。這樣就能解釋用數組下標可以取得
相應的數組中的某個元素了。(當然,在內存中還要考慮步長因素)
有了上面的分析,下面的容易弄懂了
如果聲明: int a[10] ,*p , i=2
那麼我們可以通過下面的任一種方式來訪問a[i](每一列為一組,共三種)
p=a; p=a; p=a+i;
p[i]; *(p+i); *p;
在表達式中,數組和指針是可以互換的, 因為它們在編譯器裡都是指針形式,並且都能進行去下標操作。
數組和指針的遍歷
為了更好的理解,下面通過一個例子說明數組和指針的聯系和區別。(有點難理解~哦)
將在內存訪問的角度來討論數組和指針遍歷
數組遍歷:
for(i=0;i<10;i++)
a[i]=0
遍歷過程:
1)把a的左值放入寄存器R1(也就是把a的物理地址也即數組的首地址存入R1) 可提到循環外
2)把i 的左值放入R2,同上,就是吧i 的物理地址放入R2 可移到循環外
3)把 [R2] 的右值放入 R3 也就是把變量i 的大小放入R3,(這裡有點匯編的味道)
4)如果需要,調整R3 的步長,把R1 +R3 的值放入R4 解釋:R1為數組的首地址,R3為偏移量,所以R4 就是當前操作數的地址
5)把0放入 [R4]
注:上面的R1-R4 看看做是寄存器,符號 [n] 表示的是:內存地址為n的值
”可以移到循環外“說明它在整個過程中不會改變,比如數組的首地址,變量 i 地址
左值和右值的概念在上面鏈接給出的博文中有闡述
指針遍歷:
p=a
for(i =0 ;i < 10 ; i ++)
*p++=0
遍歷過程:
1)p 所指對象的大小放入R5 可移到循環外
2)左值 p 放入R1 可移到循環外
3)[R0]放入R1
4)0存到[ R1]
5) R5+R1的結果存入R1
6)R1 存到[R0]
其實,這兩兩種訪問方式也可看做是對上一篇博客中的訪問方式的一個更深的理解。
要操作一個變量就要得到這個變量的地址,取得地址的方式數組和指針的區別和聯系。這裡就不在啰嗦,有興趣的朋友可以參見上一篇博文
http://www.cnblogs.com/yanlingyin/archive/2011/11/29/2268391.html
為什麼C把數組形參當做指針
把作為形參的數組當做指針來考慮其實是出於效率考慮。C中,所有非數組形式的數據實參均為值傳遞形式,值傳遞也就是調用函數的時候,把實參
拷貝一份給調用函數,就是說函數操作的是實參的拷貝而不是實參本身。(所以值傳遞的時候如果在函數中改變參數值,等調用結束後對實際的實參沒有
影響,因為值傳遞中函數操作的只是實參的一份拷貝而並不是實參本身)
而對於數組,如果每次執行函數都要拷貝整個數組的話,就會花費大量的時間和空間開銷,所有對於數組,C開用的機制是告訴函數數組的首地址,直接對
數組進程操作。
了解C++的朋友對於這應該就能更好地理解了,C++中參數傳遞分為值傳遞和引用傳遞,有興趣的可以自行查閱資料、這不是本文終點不在復數
數組和指針的可交換性總結:Arrays and Pointers Interchangeability Summary
1. An array access a[i] is always "rewritten" or interpreted by the compiler as a pointer access *(a+i);
2. Pointers are always just pointers; they are never rewritten to arrays. You can apply a
subscript to a pointer; you typically do this when the pointer is a function argument,
and you know that you will be passing an array in.
3. An array declaration in the specific context (only) of a function parameter can equally
be written as a pointer. An array that is a function argument (i.e., in a call to the
function) is always changed, by the compiler, to a pointer to the start of the array.
4. Therefore, you have the choice for defining a function parameter which is an array,
either as an array or as a pointer. Whichever way you define it, you actually get a
pointer inside the function.
5. In all other cases, definitions should match declarations. If you defined it as an array,
your extern declaration should be an array. And likewise for a pointer.
1)對於a [i]這種形式的訪問數組,通常被解釋為指針形式*(a + i) 也就是上文中所說的“表達式”的情形
2)指針就是指針,沒有說指針轉化為數組的情況,你可以用下標的形式去訪問指針,但一般都是指針作為函數參數時,而且傳入的是一個數組
3)在函數參數的聲明中,數組可以看做指針,(也只有這種情況)
4)當把一個數組定義為函數參數時,可以定義為數組,也可以是指針
5)其他的所有情況,聲明和定義必須匹配。如果定義了一個數組,在其他文件中對它也必須聲明為數組。指針也一樣。
參考資料:《expert c programming》
如轉載請注明出處:http://www.cnblogs.com/yanlingyin/
一條魚@博客園
2011-12-6