2014年11月28日 星期五

[c/c++] 指標的運算。為什麼指標可以相減確不能相加?

int x = 0;
int *px = &x;
int *py;
假設x所在的記憶體位址是0x1000,又假設int類型的大小為4bytes
當我們執行py = px + 1; 後,py指標中的值會等於 0x1000 + 4 = 0x1004
而執行py = px - 1; 後,py指標中的值會等於 0x1000 - 4 = 0x0FFC

由此可知當一個指標加減一個整數後,其結果還是一個指標,而不是一個整數值
而指標與整數的運算是以該指標的資料型態大小乘上整數後的值做為運算
所以
        結果指標 = 運算指標 +/- 運算值;
求結果指標的值的公式就是
        結果指標的值 = 運算指標的值 +/- (運算值 * 運算指標的資料型態大小)


再來說說指標之間的相減
就剛剛上面公式,利用簡單的數學將運算值移到=號的左邊,將結果指標移到=號的右邊
就可以得到
        運算值 = 運算指標 - 結果指標;
就剛剛求結果指標的值的公式,又可以推導出
        (運算值 * 運算指標的資料型態大小) = 運算指標的值 - 結果指標的值

舉個例子來看
假設有兩個int型態的指標pa與pb,pa指向0x2000而pb指向0x2008
r = pb - pa; 則r會是多少? 答案是2。為什麼不是8呢?
就上面的公式看來
(r * 4bytes) = 0x2008 - 0x2000
所以 r = (0x2008 - 0x2000) / 4 = 8 / 4 = 2

這裡又可以推導出另一個問題,那就是不同型態的指標之間不可以相減
例如:
char* pc;   int* pi;
int r = pi - pc;     // 編譯器會出錯
因為,就上面的公式而言,運算指標與結果指標的資料型態都是相同的,
依照求值的過程中會需要用到指標的資料型態大小,若是資料型態不同,
則編譯器不知道要以哪個為準。(其時把它當作編譯器規定的就好)

最後說明為什麼兩個指標之間不可以相加
因為,由上面的公式推導不出來兩個指標要如何相加,
也就是說兩個指標中的位址相加是無意義的。


以上屬個人觀點,若有錯誤的地方請不吝指教。

感謝WJ補充
兩個指標相減得到的距離是有號整數型態 ptrdiff_t. 注意如果因為陣列太大導致這個型態無法表示之間的距離,則相減視為嚴重的錯誤。請務必檢查 PTRDIFF_MAX 和 PTRDIFF_MIN 確保此型態滿足程式所需。

2 則留言:

  1. 有問題喔!
    一般來說兩個指標是不能相減的。
    只有在兩個指標必須指向相同陣列的元素,或是該陣列最後一個元素超過一格的位置,才能相減比較。但陣列太大也有可能出狀況。
    兩個指標相減得到的距離是有號整數型態 ptrdiff_t. 注意如果因為陣列太大導致這個型態無法表示之間的距離,則相減視為嚴重的錯誤。請務必檢查 PTRDIFF_MAX 和 PTRDIFF_MIN 確保此型態滿足程式所需。

    回覆刪除
    回覆
    1. 經實驗結果
      int *p1 = 0x1000;
      int *p2 = 0x1008;
      此時p2-p1會等於2
      而當p2 = 0x1007; 時
      p2-p1會等於1,也就是(0x1007 - 0x1000) / 4 只取商數
      因此非指向同陣列的指標應當是可以相減的

      刪除