//=====================================================================
//TITLE:
// 小議如何改變指標的指向
//AUTHOR:
// norains
//DATE:
// Sunday 4-Semtember-2011
//Environment:
// Visual Studio 2010
//=====================================================================
如何通過函數來改變傳入指標的指向?想必大家第一反應就是使用指向指標的指標作為形參,也就是說會寫類似於Func_1的函數:
void Func_1(DWORD **ppdw){ *ppdw = &g_dwVal;}
g_dwVal是一個全域變數,只要知道這個即可,其餘的暫時不用考慮。有了Func_1,那麼調用估計大家也會想到,無非是傳入一個指標,如:
pBuf = NULL;Func_1(&pBuf);
如果你認為這是C++通過函數改變指標指向的全部,那麼你就太小看它了。使用"DWORD **"能改變指標的指向,那是不是通過"DWORD *"就不行了呢?答案是否定的,形參為"DWORD *"也可以改變指標指向!不過,這個函數的寫法就有所不同,如Func_2:
void Func_2(DWORD *pdw){ *(reinterpret_cast<DWORD *>(pdw)) = reinterpret_cast<DWORD>(&g_dwVal);}
函數寫法不同,調用也要有所區別,如:
pBuf = NULL; Func_2(reinterpret_cast<DWORD *>(&pBuf));
如果你測試過這段代碼,那麼會發現即使函數形參是"DWORD *",也可以改變指標的指向!
在這裡再稍微多說一點,Func_2的函數體,其實寫成這樣也是可以正常賦值的,如:
void Func_2(DWORD *pdw){ *(reinterpret_cast<DWORD **>(pdw)) = &g_dwVal;}
我們再來看一個更加有趣的問題,如果指標的類型是BYTE,那麼是不是也能正常改變呢?所以,我們便有了一個Func_3函數:
void Func_3(BYTE *pb){ *(reinterpret_cast<DWORD **>(pb)) = &g_dwVal;}
調用的時候,自然也是有所區別:
pBuf = NULL; Func_3(reinterpret_cast<BYTE *>(&pBuf));
經過測試,這樣的方式也是能夠改變指標的指向的。估計看到這裡,應該不少朋友迷惑了,為什麼呢?在回答這個問題之前,我們繼續再看另一個更有趣的問題,不通過指標,而是通過"DWORD"類型來改變指標的指向!於是,便有了函數Func_4:
void Func_4(DWORD dwVal){ *(reinterpret_cast<DWORD *>(dwVal)) = reinterpret_cast<DWORD>(&g_dwVal);}
不用想,調用方式自然也是有區別,如:
pBuf = NULL; Func_4(reinterpret_cast<DWORD>(&pBuf));
估計很多初學者看到這裡,應該已經兩眼發暈了吧?我們不妨看看為何可以改變的真正原因。
要明白上述函數為何能夠正常改變指向,那麼就必須明白指標的地址。對於指標的地址,它其實分為兩部分,一部分是指標本身的地址,另一部分則是指標指向的地址。這樣說可能大家有點糊塗,不妨看如下的圖示:
如果是C++高手的話,那麼對於這張圖肯定是非常熟悉,但可能初學者就有點暈了。沒事,我們現在一起來看看。
假設有個指標,名為pBuf。對於圖中的0x4000 0000來說,這是指標本身的地址,以代碼錶示,便是&pBuf;而0x4000 0000這個記憶體位址儲存的0x8000 0000,便是指標指向的地址,代碼錶示為pBuf;至於0x1234 5678,不用說,就是0x8000 0000這個記憶體塊的數值了,代碼自然是*pBuf。根據這些內容,不難得出這個表:
地址/數值 |
代碼 |
0x4000 0000 |
&pBuf |
0x8000 0000 |
pBuf |
0x1234 5678 |
*pBuf |
還是以圖為例子,如果要改變指標的指向的話,那麼只需要改變0x4000 0000這個記憶體裡面的數值即可。明白這點,對於之前的函數理解就沒什麼難度了。大家不妨回頭看看,其實在調用這些Func_x函數時,傳入的都是"&pBuf",也就是指標本身的地址。既然已經知道了指標本身的地址,那麼改變指標儲存的值還有什麼問題嗎?函數所做的,只不過是一些轉換而已。
按理說,本文到此已經結束,但最後不妨再看一個初學者非常容易搞混的問題:null 指標是否佔據記憶體空間?也就是說,下面這行代碼是否佔據記憶體空間:
DWORD *pBuf = NULL;
答案是佔有空間!如果對此還有疑惑,那麼看了下面這張圖,相想必就非常明白了:
所謂的null 指標,只不過是指向記憶體位址為0x0000 0000的指標而已,和別的指標並沒有任何不同,所以肯定佔用空間。
這裡,應該還會有人迷惑,如果指向指標的指標為空白,那麼會不會佔用空間呢?也就是說下面這行代碼:
DWORD **ppBuf = NULL;
答案還是佔用空間!指向指標的指標說白了,還是指標,既然是指標就有自己本身的地址,所以肯定佔用空間!所不同的是,指向指標的指標的儲存空間儲存的是所指向的指標的地址而已。如果以第一幅圖為例子,稍微完善一下,那麼便有如示:
最後,便是全部的代碼,以供大家參考:
#include "windows.h"DWORD g_dwVal = 0x100;void Func_1(DWORD **ppdw){ *ppdw = &g_dwVal;}void Func_2(DWORD *pdw){ *(reinterpret_cast<DWORD **>(pdw)) = &g_dwVal; //*(reinterpret_cast<DWORD *>(pdw)) = reinterpret_cast<DWORD>(&g_dwVal);}void Func_3(BYTE *pb){ *(reinterpret_cast<DWORD **>(pb)) = &g_dwVal;}void Func_4(DWORD dwVal){ *(reinterpret_cast<DWORD *>(dwVal)) = reinterpret_cast<DWORD>(&g_dwVal);} int _tmain(int argc, _TCHAR* argv[]){ DWORD *pBuf = NULL; pBuf = NULL; Func_1(&pBuf); pBuf = NULL; Func_2(reinterpret_cast<DWORD *>(&pBuf)); pBuf = NULL; Func_3(reinterpret_cast<BYTE *>(&pBuf)); pBuf = NULL; Func_4(reinterpret_cast<DWORD>(&pBuf)); return 0;}