天天看點

C語言指針數組和數組指針

初學者總是分不出指針數組與數組指針的差別。其實很好了解:

指針數組:首先它是一個數組,數組的元素都是指針,數組占多少個位元組由數組本身決定。它是“儲存指針的數組”的簡稱。

數組指針:首先它是一個指針,它指向一個數組。在32 位系統下永遠是占4 個位元組,至于它指向的數組占多少位元組,不知道。它是“指向數組的指針”的簡稱。

下面到底哪個是數組指針,哪個是指針數組呢:

a)

int *p1[10];

b)

int (*p2)[10];

每次上課問這個問題,總有弄不清楚的。這裡需要明白一個符号之間的優先級問題。

“[]”的優先級比“*”要高。p1 先與“[]”結合,構成一個數組的定義,數組名為p1,int *修飾的是數組的内容,即數組的每個元素。那現在我們清楚,這是一個數組,其包含10 個指向int 類型資料的指針,即指針數組。至于p2 就更好了解了,在這裡“()”的優先級比“[]”高,“*”号和p2 構成一個指針的定義,指針變量名為p2,int 修飾的是數組的内容,即數組的每個元素。數組在這裡并沒有名字,是個匿名數組。那現在我們清楚p2 是一個指針,它指向一個包含10 個int 類型資料的數組,即數組指針。我們可以借助下面的圖加深了解:

C語言指針數組和數組指針

這裡有個有意思的話題值得探讨一下:平時我們定義指針不都是在資料類型後面加上指針變量名麼?這個指針p2 的定義怎麼不是按照這個文法來定義的呢?也許我們應該這樣來定義p2:

   int (*)[10] p2;

int (*)[10]是指針類型,p2 是指針變量。這樣看起來的确不錯,不過就是樣子有些别扭。其實數組指針的原型确實就是這樣子的,隻不過為了友善與好看把指針變量p2 前移了而已。你私下完全可以這麼了解這點。雖然編譯器不這麼想。^_^

既然這樣,那問題就來了。前面我們講過a 和&a 之間的差別,現在再來看看下面的代碼:

int main()

{

   char a[5]={'a','b','c','d'};

   char (*p3)[5] = &a;

   char (*p4)[5] = a;

   return 0;

}

上面對p3 和p4 的使用,哪個正确呢?p3+1 的值會是什麼?p4+1 的值又會是什麼?毫無疑問,p3 和p4 都是數組指針,指向的是整個數組。&a 是整個數組的首位址,a是數組首元素的首位址,其值相同但意義不同。在c 語言裡,指派符号“=”号兩邊的資料類型必須是相同的,如果不同需要顯示或隐式的類型轉換。p3 這個定義的“=”号兩邊的資料類型完全一緻,而p4 這個定義的“=”号兩邊的資料類型就不一緻了。左邊的類型是指向整個數組的指針,右邊的資料類型是指向單個字元的指針。在visual c++6.0 上給出如下警告:

   warning c4047: 'initializing' : 'char (*)[5]' differs in levels of indirection from 'char *'。

還好,這裡雖然給出了警告,但由于&a 和a 的值一樣,而變量作為右值時編譯器隻是取變量的值,是以運作并沒有什麼問題。不過我仍然警告你别這麼用。

既然現在清楚了p3 和p4 都是指向整個數組的,那p3+1 和p4+1 的值就很好了解了。

但是如果修改一下代碼,會有什麼問題?p3+1 和p4+1 的值又是多少呢?

   char (*p3)[3] = &a;

   char (*p4)[3] = a;

甚至還可以把代碼再修改:

   char (*p3)[10] = &a;

   char (*p4)[10] = a;

這個時候又會有什麼樣的問題?p3+1 和p4+1 的值又是多少?

上述幾個問題,希望讀者能仔細考慮考慮。

先看下面這個例子:

struct test

   int num;

   char *pcname;

   short sdate;

   char cha[2];

   short sba[4];

}*p;

假設p 的值為0x100000。如下表表達式的值分别為多少?

   p + 0x1 = 0x___ ?

   (unsigned long)p + 0x1 = 0x___?

   (unsigned int*)p + 0x1 = 0x___?

我相信會有很多人一開始沒看明白這個問題是什麼意思。其實我們再仔細看看,這個知識點似曾相識。一個指針變量與一個整數相加減,到底該怎麼解析呢?

還記得前面我們的表達式“a+1”與“&a+1”之間的差別嗎?其實這裡也一樣。指針變量與一個整數相加減并不是用指針變量裡的位址直接加減這個整數。這個整數的機關不是byte 而是元素的個數。是以:p + 0x1 的值為0x100000+sizof(test)*0x1。至于此結構體的大小為20byte,前面的章節已經詳細講解過。是以p +0x1 的值為:0x100014。

(unsigned long)p + 0x1 的值呢?這裡涉及到強制轉換,将指針變量p 儲存的值強制轉換成無符号的長整型數。任何數值一旦被強制轉換,其類型就改變了。是以這個表達式其實就是一個無符号的長整型數加上另一個整數。是以其值為:0x100001。

(unsigned int*)p + 0x1 的值呢?這裡的p 被強制轉換成一個指向無符号整型的指針。是以其值為:0x100000+sizof(unsigned int)*0x1,等于0x100004。

上面這個問題似乎還沒啥技術含量,下面就來個有技術含量的:在x86 系統下,其值為多少?

intmain()

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

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

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

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

這是我講課時一個學生問我的題,他在網上看到的,據說難倒了n 個人。我看題之後告訴他,這些人肯定不懂彙編,一個懂彙編的人,這種題實在是小case。下面就來分析分析這個問題:

根據上面的講解,&a+1 與a+1 的差別已經清楚。

ptr1:将&a+1 的值強制轉換成int*類型,指派給int* 類型的變量ptr,ptr1 肯定指到數組a 的下一個int 類型資料了。ptr1[-1]被解析成*(ptr1-1),即ptr1 往後退4 個byte。是以其值為0x4。

ptr2:按照上面的講解,(int)a+1 的值是元素a[0]的第二個位元組的位址。然後把這個位址強制轉換成int*類型的值賦給ptr2,也就是說*ptr2 的值應該為元素a[0]的第二個位元組開始的連續4 個byte 的内容。

其記憶體布局如下圖:

C語言指針數組和數組指針

好,問題就來了,這連續4 個byte 裡到底存了什麼東西呢?也就是說元素a[0],a[1]裡面的值到底怎麼存儲的。這就涉及到系統的大小端模式了,如果懂彙編的話,這根本就不是問題。既然不知道目前系統是什麼模式,那就得想辦法測試。大小端模式與測試的方法在第一章講解union 關鍵字時已經詳細讨論過了,請翻到彼處參看,這裡就不再詳述。我們可以用下面這個函數來測試目前系統的模式。

int checksystem( )

   union check

   {

      int i;

     char ch;

   } c;

   c.i = 1;

   return (c.ch ==1);

如果目前系統為大端模式這個函數傳回0;如果為小端模式,函數傳回1。也就是說如果此函數的傳回值為1 的話,*ptr2 的值為0x2000000。如果此函數的傳回值為0 的話,*ptr2 的值為0x100。

繼續閱讀