指針運算探究

 

有以下程序:

 

int main()
{
	int arr[] = {1,2,3,4,5,6,7,8,9,10};
	int *p = arr;
	*p = 10;
	p++;
	*p = 20;
	printf("十進制 %d,%d\n",arr[0],arr[1]);
    printf("十六進制 %08x,%08x\n",arr[0],arr[1]);
	return 0;
}

我們暫時先不要去考慮其答案,而是先來探究中間這個“p++”在這裏是什麼意思?

在該程序中,我們定義了一個整型數組,然後定義了一個整形指針變量,其指向的是該數組的首元素地址,所以之後對其解引用“*p=10”,自然會使arr[0]=10,這是沒有問題的。之後重點來了,這裏的“p++”到底給p加了個什麼東西?我們有以下猜想:

1.    加了整個數組的長度?

2.    加了一個字節的長度?

3.    加了四個字節的長度?

首先我們很容易把 1否決,因爲這是不可能的,如果一下子給P加整個數組的長度,p就會從數組的最前端直接跳到末尾,這種結果對我們來說是毫無意義的。

那麼 2 就有點意思了,加一個字節的長度會發生什麼呢?

我們知道,整形數組中,每個元素需要用四個字節來保存,我們用下面的圖像來簡略表示arr[0]= 10和arr[1] = 2

 

其中,我們用紅色字體假設其地址,每個格子所佔空間爲四個字節。接下來我們把這個圖片放大四倍,即每個格子所佔空間變爲一個字節。如圖:

之後我們便需要把10和2分別放入四個格子中,這其實很簡單。

例如10,由於是四個格子,每個格子一個字節,即要用32位表示10 ,但每個格子寫8個數字太麻煩了,所以我們用十六進制 表示10,這樣剛好每兩個十六進制數字放一個格子,我們知道10的十六進制是00 00 00 0a . 同理得出 2 的十六進制 00 00 00 02 .

 

最後重要的來了,是不是直接把這八對十六進制數字按次序放進去呢?當然不是!因爲存在大小端的問題!低地址放大數據是大端,低地址放小數據是小端,而我們的PC正好是小端,所以要在低地址處放入小的數據。那麼0x0000000a和0x00000002應該怎麼放呢?如下圖:

也許有人不明白,舉個例子:十進制的10007中1和7 誰大?你可能會說 7 大,但你不能只看數字而不看位數啊,7是7,可這裏的1是10000啊,所以如果也要把10007按小端儲存,就應該把 7 放到最前面 ,1放到最後面。所以你現在明白爲甚麼上面的十六進制是爲什麼這麼儲存的了吧?

然後我們繼續,讓一級指針 p 加一個字節,那麼解引用後賦予的值應該在下圖綠框的地方,

即我們要把20放入綠框中,有了上面的經驗,步驟就很清晰了,首先,20的十六進制是00 00 00 14,然後按照小端低地址放小數據的規則,有以下結果:

 

看到這裏,想必大家已經知道,“加一個字節”這個猜想是絕對站不住腳的,“p++”絕對不可能整出這麼複雜的玩意兒出來,只加一個字節,只會使指針錯位。不過,既然算到了這裏,我們就來看看這真正只加一個字節會對結果產生什麼影響,接下來的程序如下:

 

int main()
{
	int arr[] = {1,2,3,4,5,6,7,8,9,10};
	int *p = arr;
	*p = 10;
	p = (int *)((int)p+1); //該語句是讓 p 只加一個字節
	*p = 20;
	printf("%08x,%08x\n",arr[0],arr[1]); //輸出arr[0] 、arr[1] 的八位十六進制

	return 0;
}

其輸出結果爲0000140a,00000000

別驚訝爲甚麼是反的…..因爲你儲存是按照小端儲存,輸出是正常的啊….

該結果跟我們的計算結果是一樣的,有的同學還想知道其十進制的值,只要把上面輸出語句裏的“%08x”改爲“%d”即可,其輸出結果是5130,0

這裏就可以更直觀地看出其不靠譜了,10和2怎麼可能變成這麼大的數字?

那麼最後,我們再回過頭看本篇文章開頭的那道程序,結果我就在這裏公佈了

十進制  10,20

十六進制0000000a,00000014

 

看到這兩個十六進制數字,是不是感到莫名親切?

 

 

 

總結:指針與數字間的加法,需要進行調整,其調整權重爲指針所指向的類型長度,如:上面的例子,其權重就爲sizeof(int)

           總的計算式爲:指針+(數字 * 權重)。

          指針與數字間的減法,與指針加法類似,在此不贅述。其總的計算式爲:指針 -(數字 * 權重)

 

接下來,我們討論指針與指針之間的運算。

 

首先,我們需要知道的是,每一個運算都有其意義,例如:指針和一個數字進行加減運算,其意義是改變了指針指向的地址,使其嚴格指向下一個數據。而我們現在需要考慮指針之間的運算是什麼含義?


我們先看 “指針+指針” ,這個運算有意義嗎?顯然是沒有的,我們拿時間來作比喻,下午三點加下午四點得出來的值是什麼?你肯定會是七,但這個值有意義嗎?就算你說這個七是七點的意思,但時鐘裏每一個時刻都是獨立的,兩個時刻相加,你只是把他們的值加起來,就其本身而言並無意義。所以指針之間不存在加法運算。

 

然後我們看 “指針-指針”,還是拿時間來舉例子,下午七點,減去下午三點,得出來的值是什麼含義? 答案是四。你能說這代表的是四點這個時刻嗎?並不是,這個四代表的是,三點和七點之間間隔着四個小時。

 

到這裏指針之間減法的意義就呼之欲出了,兩個指針相減得出的應該是兩者之間元素的個數。


指針之間相減其計算式爲:(指針 - 指針)/權重


下面是關於指針運算的程序,僅作爲練習題,其結果已寫明,請自行對照。

 

 

 

指針 + 數字:

 

int main()
{
	int *p = (int *)2000;
	printf("%d\n",p+4);//2016
	printf("%d\n",(short *)p+4);//2008
	printf("%d\n",(double *)p+4);//2032
	printf("%d\n",(float **)p+4);//2016
	printf("%d\n",(unsigned short *)p+4);//2008
	printf("%d\n",(long *)p+4);//2016
	printf("%d\n",(char *)p+4);//2004
	printf("%d\n",(unsigned long long)p+4);//2004
	return 0;
}

指針 - 數字:

int main()
{
	int *p = (int *)0x2010;
	printf("%x\n",p-2);//2008
	printf("%x\n",(short *)p-2);//200c
	printf("%x\n",(unsigned long *)p-2);//2008
	printf("%x\n",(long long **)p-2);//2008
	printf("%x\n",(float *)p-2);//2008
	printf("%x\n",(double *)p-2);//2000
	printf("%x\n",(char *)p-2);//200e
	printf("%x\n",(unsigned long )p-2);//200e

	return 0;
}

 

指針 - 指針:

int main()
{
	int arr[10] = {1};
	int *p = &arr[1];
	int *q = &arr[9];
	printf("%d\n",p-q);//-8
	printf("%d\n",q-p);//8
	printf("%d\n",(short *)q-(short *)p);//16
	printf("%d\n",(double *)q-(double *)p);//4
	printf("%d\n",(int ***)q-(int ***)p);//8
	printf("%d\n",(char **)q-(char **)p);//8
	printf("%d\n",(long)q-(long)p);//32

	return 0;
}

其結果皆爲標準答案,若不符請仔細思考,請特別注意*的存在與否以及各類型的字節數!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章