首先看如下代碼:
1 int main(int argc, char** argv) 2 { 3 int a[5] = {1,2,3,4,5}; 4 int* ptr = (int*)(&a + 1); 5 printf("%d,%d\n", *(a+1), *(ptr-1)); 6 return 0; 7 }
這道題在很多所謂經典C語言面試題里是常見的不能再常見,你知道輸出結果嗎?
答案是:2,5
但是仍有許多人不能答對,也包括當初的我。這道題簡簡單單,但是考察了不少於如下內容:數組指針、數組首地址概念、數組指針和數組首地址和數組首元素地址之間的關系,指針運算規則,指針類型,int型長度,指針長度,類型轉換…這些概念如果有一個及以上不是那么太清楚的話,很容易答錯。
為方便討論,先開始理解如下關系:
以下是某次在Ubuntu 10.10-desktop-i386 + gcc 4.4.5的運行結果:
size of a: 20 (bytes) (Why? 因為機器是32位的, size of int 為 4 bytes,a有5個int)
size of ptr: 4 (bytes) (ptr是指針,永遠不要忘記,32位字長的機器,指針長度是4)
ptr的地址:bfb5c52c (取決於具體情況,這里只是為了方便理解和討論)
a的地址(&a):bfb5c518
a[0]的地址:bfb5c518
a[1]的地址:bfb5c51c
&a + 1:bfb5c52c (&a[0]:bfb5c518 -- &a[1]:bfb5c51c -- &a[2]:bfb5c520 -- &a[3]:bfb5c524 -- &a[4]:bfb5c528 -- &a[5]:bfb5c52c)
注意&a[5]只是這里的書面表示,其實已經在數組范圍之外了,當然利用數組地址a取a[5]的地址本身是合法的。
下面具體解釋:
- a是數組地址,根據C語言語義,即數組首元素的地址,首元素是int類型,故首元素地址應為int*類型,故a的類型為int*;
- &a是數組指針,其值與a相同,但含義卻不同,即類型不同,各位能准確給出&a的類型嗎?是 int (*)[5],難理解嗎?還記得我那篇《C堆上申請二維數組》里面提到的a的類型嗎?這正是C語言指針較晦澀和難懂的地方。
理解了這兩點的話,上述問題則十分簡單:
a + 1:現在把a看作一個指針,指針+1操作,根據C語言語義,實際增加偏移量的是指針指向類型的長度,即32位機器下的int型,即4字節,故a+1就是&a[1],那么*(a+1)就是a[1],即2;
&a + 1:現在把&a看作一個指針(不知道怎么“看作”? int (*b)[5] = &a),那么&a + 1實際增加的偏移量是其指向類型(int (*)[5])的長度,即20字節,故&a + 1其實是a+5,即&a[5](雖然a[5]是越界訪問,但&a[5]沒啥問題,這就是C語言)。
好了,現在已知&a+1的值是a+5,但其類仍然是int (*)[5],現在ptr指向&a+1且通過強制類型轉換變成了int*,那么ptr-1是什么呢?因為ptr已經是int*類型了,ptr-1即向前移動4字節,即a+4,即&a[4],那么*(ptr-1)就是a[4],即5。
是否覺得十分困難?慢慢來吧。這也正是C語言難和強大的冰山一角。
再補充一句:以下關系的值(地址)是一樣的:
a = &a = &a[0],不信可以試試喔!