指針數組

int main(void)

{

    int a[4] = {1, 2, 3, 4};

    int *ptr1=(int *)(&a+1);

    int *ptr2=(int *)((int)a+1);

    printf("%x, %x/n", ptr1[-1], *ptr2);

    return 0;

}

問,在x86平臺下輸出啥?


 

題目雖然噁心了點,但作爲一個例子來分析,還是挺好玩的。學過C語言的朋友可以暫且不看下文,自己試着分析一下,看看結果跟我的是否一樣,也不失爲一件趣事。

下面把這道題牽涉到的所有邊邊角角的問題梳理一遍,詳細討論如下:

1、&a+1

首先明確,a是一個具有4個整型變量的數組的名字,具體地說是這種數組的首元素的首地址,而&a是數組的首地址,請注意措辭。而關於指針加1,則需要指針運算的知識。沒學過指針運算或者已經忘記了這個知識點的朋友們,下面就是關於指針運算你需要知道的事實:

就像上面的例子那樣,式子&a+1表示的是指針加法運算,而不是普通的數值加法運算,之所以會這樣是因爲&a是一個指針而非普通數值(雖然它本質上也是一個整數)。那麼你會問:加入此時&a=0xFFFF5700,那麼&a+1是多少呢?答案是:取決於&a的類型 

    a) 如果&a是一個指向char型的指針,那麼&a+1 = 0xFFFF5701

    b) 如果&a是一個指向short型的指針,那麼&a+1 = 0xFFFF5702

    c) 如果&a是一個指向int型的指針,那麼&a+1 = 0xFFFF5704 (32位機器)

    d) 如果&a是一個指向某種結構體struct foo的指針,那麼&a+1 = 0xFFFF5700+sizeof(struct foo)

    ……

還沒看出端倪嗎?沒錯,指針加1不是指針內容簡單地加1,而是讓指針指向下一個數據 ,加2就是讓指針指向下兩個數據,這個數據的類型就是指針指向的類型,所以指針的加法究竟會讓這個指針指向哪裏,取決於這個指針指向的數據類型。

 

因此,綜上所述,當&a與整數1做加法時,實際上是指針的加法,加1的含義是:令指針a指向下一個數據 ,下一個數據是啥?當然是緊挨着的下一個具有4個整型變量的數組了(因爲&a的類型是指向具有4個整型變量的數組的指針嘛),於是a的指向了4的下一個地址,在用此值初始化ptr1,因此ptr1的指向如圖所示:

圖片

由於在ptr1初始化的時候,令&a+1強制轉換成整型指針(關於類型轉換的詳細討論請參考http://blog.csdn.net/seton040/archive/2009/10/19/4699869.aspx),因此ptr1[-1]相當於把ptr1往前挪一個整型大小,即4個字節。 如下圖:

圖片

顯然,打印出的第一個數字是a[3]的內容,即數值4.

當然,我們還必須說明一個事實:數組下標是可以爲負數的,實際上,取下標符“[ ]”的內部實現,就是指針運算!比如a[2],等價於*(a+2),即以a地址爲基址,取偏移量爲2的地址的值。所以ptr1[-1]等價於*(ptr-1)。

 

2、(int *)((int)a+1)

至於指針ptr2的處理更噁心一點,呵呵!它先是把數組名a強制轉換成整型變量,然後再加1,然後再強制轉換成整型指針!真羅嗦,但不要緊,咱有的是耐性,無非就是讓ptr2指向a[0]的第二個字節罷了,此時ptr2指向如下圖所示:

圖片

顯然,此時打印的內容就是ptr2所指向的往後4個字節的內容了,也就是a{0}的後三個字節和a[1]的第一個字節,那究竟會打印出啥玩意兒呢?上面的圖沒有畫出裏面的內容,要是把每個字節的內容都畫出來就好了,呵呵!

 

要把上圖中每一個字節的內容都打印出來沒問題,但是你先要知道字節序 的概念,字節序分兩種,一種叫大端字節序(big-endian) ,當然除此之外必然有小端 字節序(little-endian) ,讓我們用一個問題,來引出字節序的概念,然後再來搞定這兩個小鬼吧!

 

問題是這樣的:對多字節存儲的變量,機器是如何做出解釋的??請看下圖:

圖片

假如這是一個普通的int變量,地球人都知道,在32機器上int佔用4個字節存儲數據,就像上圖中顯示的那樣,在4個字節中放置了一堆數字,但是機器究竟會把這個數解釋成0x0103070F呢,還是解釋成0x0F070301呢?答案是:都有可能!

官方解釋:

    1、所謂大端(big-endian)序,就是高優先位 對應高有效位 

    2、所謂小端(little-endian)序,就是高優先位 對應低有效位 

民間解釋:

    1、所謂大端(big-endian)序,就是讀取或者存放數據時,最低 位 對應 高地址 

    2、所謂小端(big-endian)序,就是讀取或者存放數據時,最低 位 對應 低地址 

如,要把0x0103070F存放進存儲器中時,如果把0E放進高地址處則是小端序,如果把0F放進低地址處則是大端序。照此,上圖中存放的數據如果被機器理解成0x0103070F則是該機器是大端序的,否則若被理解成0x0F070301則是小端序的。

 

回到原來的問題,此時ptr2指向了a[0]的第二個字節。我們以x86平臺爲例(小端序),此時其內部數據分佈是這樣的:

圖片

由於x86平臺是小端序的,根據咱剛剛討論過的理論,小端序的存取時最低 位 對應 低地址 ,因此將會打印出0200 0000,如果題目中沒有說明在x86平臺,那答案是不確定的,取決於具體的平臺,例如ARM平臺就是大端序的。

 

到此這個試題基本算是圓滿解決了,但是我還想再羅嗦一下,關於代碼中的那個a,以及那個&a,估計還是有很多朋友心裏不怎麼平坦吧,每次遇到數組啊指針啊,雖然程序好像彷彿貌似也沒出啥毛病,但總感覺是混過去的,這次我們要徹底解決它。記得某人說過,一個真正的程序員,必須對自己寫的代碼的每一個細節瞭如指掌。

再來考慮這個定義:



int a[4] = {1, 2, 3, 4};

 


這時我們必須明確,編譯器根據我們提供的類型和數組大小,爲我們分配了適當大小的存儲區域,並且把這塊存儲區域叫做a,請注意 :&a,就像我們上面提到的,它的類型是“指向具有4個整型變量數組的指針“,簡而言之&a是一個數組指針。這個數組的名字a,當它作爲右值(簡單而言,出現在賦值號“=”左邊的就是左值,出現在賦值號“=”右邊的就是右值)時代表的是數組首元素的首地址,而不是數組的首地址。此時a的意義跟&a[0]是等價的,都是指向首元素的指針。記住咯!

數組的名字會在另一個地方與指針等價,那就是函數參數表,當出現在函數的參數表裏面的時候,不管你寫的是數組,還是指針,一旦進入函數內部,通通變成指針。

發佈了0 篇原創文章 · 獲贊 6 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章