計算機自己是不認識程序中給的變量名,無論咱們以何種方式給變量命名,最終都會轉化爲相應的地址,編譯器會生成一些符號常量而且與對應的地址相關聯,以達到訪問變量的目的。程序員
變量是在內存中用來存儲數據以供程序使用,變量主要有兩個部分構成:變量名、變量類型,其中變量名對應了一塊具體的內存地址,而變量類型則代表該如何翻譯內存中存儲的二級制數。咱們知道不一樣的類型翻譯爲二進制的值不一樣,好比整型是直接經過數學轉化、浮點數是採用IEEE的方法、字符則根據ASCII碼轉化,一樣變量類型決定了變量所佔的內存大小,以及如何在二進制和變量所表達的真正意義之間轉化。而指針變量也是一個變量,在內存中也佔空間,不過比較特殊的是它存儲的是其餘變量的地址。在32位的機器中,每一個進程能訪問4GB的內存地址空間,因此程序中的地址採用32位二進制數表示,也就是一個整型變量的長度,地址值通常沒有負數因此準確的說指針變量的類型應該是unsigned int 即每一個指針變量佔4個字節。還記得在定義結構體中可使用該結構體的指針做爲成員,可是不能使用該結構的實例做爲成員嗎?這是由於編譯器須要根據各個成員變量的大小分配相關的內存,用該結構體的實例做爲成員時,該結構體根本沒有定義完整,編譯器是不會知道該如何分配內存的,而任何類型的指針都只佔4個字節,編譯器天然知道如何分配內存。咱們在書寫指針變量時給定的類型是它所指向的變量的類型,這個類型決定了如何翻譯所對應內存中的值,以及該訪問多少個字節的內存。對指針的間接訪問會先先取出值,訪問到對應的內存,再根據指針所指向的變量的類型,翻譯成對應的值。通常指針只能指向對應類型的變量,好比int類型的指針只能指向int型的變量,而有一種指針變量能夠指向全部類型的變量,它就是void類型的指針變量,可是因爲這種類型的變量沒有指定它所對應的變量的類型,因此即便有了對應的地址,它也不知道該取多大內存的數據,以及如何解釋這些數據,因此這種類型的指針不支持間接訪問,下面是一個間接訪問的例子:數組
int main() { int nValue = 10; float fValue = 10.0f; char cValue = 'C'; int *pnValue = &nValue; float *pfValue = &fValue; char *pcValue = &cValue; printf("pnValue = %x, *pnValue = %d\n", pnValue, *pnValue); printf("pfValue = %x, *pfValue = %f\n", pfValue, *pfValue); printf("pcValue = %x, *pcValue = %c\n", pcValue, *pcValue); return 0; }
下面是它對應的反彙編代碼(部分):數據結構
10: int nValue = 10; 00401268 mov dword ptr [ebp-4],0Ah 11: float fValue = 10.0f; 0040126F mov dword ptr [ebp-8],41200000h 12: char cValue = 'C'; 00401276 mov byte ptr [ebp-0Ch],43h 13: int *pnValue = &nValue; 0040127A lea eax,[ebp-4] 0040127D mov dword ptr [ebp-10h],eax 14: float *pfValue = &fValue; 00401280 lea ecx,[ebp-8] 00401283 mov dword ptr [ebp-14h],ecx 15: char *pcValue = &cValue; 00401286 lea edx,[ebp-0Ch] 00401289 mov dword ptr [ebp-18h],edx 16: printf("pnValue = %x, *pnValue = %d\n", pnValue, *pnValue); 0040128C mov eax,dword ptr [ebp-10h] 0040128F mov ecx,dword ptr [eax] 00401291 push ecx 00401292 mov edx,dword ptr [ebp-10h] 00401295 push edx 00401296 push offset string "pnValue = %x, *pnValue = %d\n" (00432064) 0040129B call printf (00401580) 004012A0 add esp,0Ch
從上面的彙編代碼能夠看到指針變量會佔內存空間,它們的地址分別是:[ebp - 10h] 、 [ebp - 14h]、 [ebp - 18h],在給指針變量賦值時首先將變量的地址賦值給臨時寄存器,而後將寄存器的值賦值給指針變量,而經過間接訪問時也通過了一個臨時寄存器,先將指針變量的值賦值給臨時寄存器(mov eax,dword ptr [ebp-10h])而後經過這個臨時寄存器訪問變量的地址空間,獲得變量值( mov ecx,dword ptr [eax]),因爲間接訪問進過了這幾步,因此在效率上是比不上直接使用變量。下面是對char型變量的間接訪問:函數
004012BF mov edx,dword ptr [ebp-18h] 004012C2 movsx eax,byte ptr [edx] 004012C5 push eax
首先也是將指針變量的值取出來,放到寄存器中,而後根據寄存器尋址找到變量對應的地址,訪問變量。其中」bye ptr「表示只操做該地址中的一個字節。spa
對於地址咱們能夠進行加法和減法操做,地址的加法主要用於向下尋址,通常用於數組等佔用連續內存空間的數據結構,通常是地址加上一個數值,表示向後偏移必定的單位,指針一樣也有這樣的操做,可是與地址值不一樣的是指針每加一個單位,表示向後偏移一個元素,而地址值加1則就是在原來的基礎上加上一。指針偏移是根據其所指向的變量類型來決定的,好比有下面的程序:翻譯
int main(int argc, char* argv[]) { char szBuf[5] = {0x01, 0x23, 0x45, 0x67, 0x89}; int *pInt = (int*)szBuf; short *pShort = (short*)szBuf; char *pChar = szBuf; pInt += 1; pShort += 1; pChar += 1; return 0; }
它的彙編代碼以下:指針
9: char szBuf[5] = {0x01, 0x23, 0x45, 0x67, 0x89}; 00401028 mov byte ptr [ebp-8],1 0040102C mov byte ptr [ebp-7],23h 00401030 mov byte ptr [ebp-6],45h 00401034 mov byte ptr [ebp-5],67h 00401038 mov byte ptr [ebp-4],89h 10: int *pInt = (int*)szBuf; 0040103C lea eax,[ebp-8] 0040103F mov dword ptr [ebp-0Ch],eax 11: short *pShort = (short*)szBuf; 00401042 lea ecx,[ebp-8] 00401045 mov dword ptr [ebp-10h],ecx 12: char *pChar = szBuf; 00401048 lea edx,[ebp-8] 0040104B mov dword ptr [ebp-14h],edx 13: 14: pInt += 1; 0040104E mov eax,dword ptr [ebp-0Ch] 00401051 add eax,4 00401054 mov dword ptr [ebp-0Ch],eax 15: pShort += 1; 00401057 mov ecx,dword ptr [ebp-10h] 0040105A add ecx,2 0040105D mov dword ptr [ebp-10h],ecx 16: pChar += 1; 00401060 mov edx,dword ptr [ebp-14h] 00401063 add edx,1 00401066 mov dword ptr [ebp-14h],edx
根據其彙編代碼能夠看出,對於int型的指針,每加1個會向後偏移4個字節,short會偏移2個字節,char型的會偏移1個,因此根據以上的內容,能夠得出一個公式:TYPE* P p + n = p + sizeof(TYPE) *ncode
根據上面的加法公式咱們能夠推導出兩個指針的減法公式,TYPE *p1, TYPE* p2: p2 - p1 = ((int)p2 - (int)p1) / sizeof(TYPE),兩個指針相減獲得的結果是兩個指針之間擁有元素的個數。只有同類型的指針之間才能夠相減。而指針的乘除法則沒有意義,地址之間的乘除法也沒有意義。blog
引用是在C++中提出的,是變量的一個別名,提出引用主要是但願減小指針的使用,引用於指針在一個函數中想上述例子中那樣使用並無太大的意義,大量使用它們是在函數中,做爲參數傳遞,不只能夠節省效率,同時也能夠傳遞一段緩衝,做爲輸出參數來使用。這大大提高了程序的效率以及靈活性。可是在一些新手程序員看來指針無疑是噩夢般的存在,因此C++引入了引用,但願代替指針。在通常的C++書中都說引用是變量的一個別名是不佔內存的,可是我經過查看反彙編代碼發現引用並非向書上說的那樣,下面是一段程序及它的反彙編代碼:進程
int nValue = 10; int &rValue = nValue; printf("%d\n", rValue);
10: int nValue = 10; 00401268 mov dword ptr [ebp-4],0Ah 11: int &rValue = nValue; 0040126F lea eax,[ebp-4] 00401272 mov dword ptr [ebp-8],eax 12: printf("%d\n", rValue); 00401275 mov ecx,dword ptr [ebp-8] 00401278 mov edx,dword ptr [ecx] 0040127A push edx 0040127B push offset string "%d\n" (0042e01c) 00401280 call printf (00401520)
從彙編代碼中能夠看到,在定義引用併爲它賦值的過程當中,編譯器實際上是將變量的地址賦值給了一個新的變量,這個變量的地址是[ebp - 8h],在調用printf函數的時候,編譯器將地址取出並將它壓到函數棧中。下面是將引用改成指針的狀況:
10: int nValue = 10; 00401268 mov dword ptr [ebp-4],0Ah 11: int *pValue = &nValue; 0040126F lea eax,[ebp-4] 00401272 mov dword ptr [ebp-8],eax 12: printf("%d\n", *pValue); 00401275 mov ecx,dword ptr [ebp-8] 00401278 mov edx,dword ptr [ecx] 0040127A push edx 0040127B push offset string "%d\n" (0042e01c) 00401280 call printf (00401520)
兩種狀況的彙編代碼徹底同樣,也就是說引用其實就是指針,編譯器將其包裝了一下,使它的行爲變得和使用變量相同,並且在語法層面上作了一個限制,引用在定義的時候必須初始化,且初始化完成後就不能指向其餘變量,這個行爲與常指針相同。