分享一篇很好的C指針文章,查缺補漏!
來源:網(wǎng)絡指針對于C來說太重要。然而,想要全面理解指針,除了要對C語言有熟練的掌握外,還要有計算機硬件以及操作系統(tǒng)等方方面面的基本知識。所以本文盡可能的通過一篇文章完全講解指針。
為什么需要指針?
指針解決了一些編程中基本的問題。
第一,指針的使用使得不同區(qū)域的代碼可以輕易的共享內(nèi)存數(shù)據(jù)。當然小伙伴們也可以通過數(shù)據(jù)的復制達到相同的效果,但是這樣往往效率不太好。
因為諸如結構體等大型數(shù)據(jù),占用的字節(jié)數(shù)多,復制很消耗性能。
但使用指針就可以很好的避免這個問題,因為任何類型的指針占用的字節(jié)數(shù)都是一樣的(根據(jù)平臺不同,有4字節(jié)或者8字節(jié)或者其他可能)。
第二,指針使得一些復雜的鏈接性的數(shù)據(jù)結構的構建成為可能,比如鏈表,鏈式二叉樹等等。
第三,有些操作必須使用指針。如操作申請的堆內(nèi)存。
還有:C語言中的一切函數(shù)調(diào)用中,值傳遞都是“按值傳遞”的。
如果我們要在函數(shù)中修改被傳遞過來的對象,就必須通過這個對象的指針來完成。
計算機是如何從內(nèi)存中進行取指的?
計算機的總線可以分為3種:數(shù)據(jù)總線,地址總線和控制總線。這里不對控制總線進行描述。數(shù)據(jù)總線用于進行數(shù)據(jù)信息傳送。數(shù)據(jù)總線的位數(shù)一般與CPU的字長一致。一般而言,數(shù)據(jù)總線的位數(shù)跟當前機器int值的長度相等。例如在16位機器上,int的長度是16bit,32位機器則是32bit。這個計算機一條指令最多能夠讀取或者存取的數(shù)據(jù)長度。大于這個值,計算機將進行多次訪問。這也就是我們說的64位機器進行64位數(shù)據(jù)運算的效率比32位要高的原因,因為32位機要進行兩次取指和運行,而64位機卻只需要一次!
地址總線專門用于尋址,CPU通過該地址進行數(shù)據(jù)的訪問,然后把處于該地址處的數(shù)據(jù)通過數(shù)據(jù)總線進行傳送,傳送的長度就是數(shù)據(jù)總線的位數(shù)。地址總線的位數(shù)決定了CPU可直接尋址的內(nèi)存空間大小,比如CPU總線長32位,其最大的直接尋址空間長232KB,也就是4G。這也就是我們常說的32位CPU最大支持的內(nèi)存上限為4G(當然,實際上支持不到這個值,因為一部分尋址空間會被映射到外部的一些IO設備和虛擬內(nèi)存上?,F(xiàn)在通過一些新的技術,可以使32位機支持4G以上內(nèi)存,但這個不在這里的討論范圍內(nèi))。
一般而言,計算機的地址總線和數(shù)據(jù)總線的寬度是一樣的,我們說32位的CPU,數(shù)據(jù)總線和地址總線的寬度都是32位。
計算機訪問某個數(shù)據(jù)的時候,首先要通過地址總線傳送數(shù)據(jù)存儲或者讀取的位置,然后在通過數(shù)據(jù)總線傳送需要存儲或者讀取的數(shù)據(jù)。一般地,int整型的位數(shù)等于數(shù)據(jù)總線的寬度,指針的位數(shù)等于地址總線的寬度。?計算機的基本訪問單元?學過C語言的人都知道,C語言的基本數(shù)據(jù)類型中,就屬char的位數(shù)最小,是8位。我們可以認為計算機以8位,即1個字節(jié)為基本訪問單元。小于一個字節(jié)的數(shù)據(jù),必須通過位操作來進行訪問。?內(nèi)存訪問方式
如圖1所示,計算機在進行數(shù)據(jù)訪問的時候,是以字節(jié)為基本單元進行訪問的,所以可以認為,計算每次都是從第p個字節(jié)開始訪問的。訪問的長度將由編譯器根據(jù)實際類型進行計算,這在后面將會進行講述。?內(nèi)存訪問方式?想要了解更多,就去翻閱計算機組成原理和編譯原理吧。?sizeof關鍵字
sizeof關鍵字是編譯器用來計算某些類型的數(shù)據(jù)的長度的,以字節(jié)為基本單位。例如:
sizeof(char)=1;
sizeof(int)=4;
sizeof(Type)的值是在編譯的時候就計算出來了的,可以認為這是一個常量!
指針是什么?
我們知道:C語言中的數(shù)組是指一類類型,數(shù)組具體區(qū)分為 ?int 類型數(shù)組,double類型數(shù)組,char數(shù)組 等等。
同樣指針這個概念也泛指一類數(shù)據(jù)類型,int指針類型,double指針類型,char指針類型等等。
通常,我們用int類型保存一些整型的數(shù)據(jù),如 int num = 97 , 我們也會用char來存儲字符:char ch = 'a'。
我們也必須知道:任何程序數(shù)據(jù)載入內(nèi)存后,在內(nèi)存都有他們的地址,這就是指針。
而為了保存一個數(shù)據(jù)在內(nèi)存中的地址,我們就需要指針變量。
因此:指針是程序數(shù)據(jù)在內(nèi)存中的地址,而指針變量是用來保存這些地址的變量。
?
在我個人的理解中,可以將指針理解成int整型,只不過它存放的數(shù)據(jù)是內(nèi)存地址,而不是普通數(shù)據(jù),我們通過這個地址值進行數(shù)據(jù)的訪問,假設它的是p,意思就是該數(shù)據(jù)存放位置為內(nèi)存的第p個字節(jié)。
當然,我們不能像對int類型的數(shù)據(jù)那樣進行各種加減乘除操作,這是編譯器不允許的,因為這樣錯是非常危險的!
圖2就是對指針的描述,指針的值是數(shù)據(jù)存放地址,因此,我們說,指針指向數(shù)據(jù)的存放位置。?
指針的長度
我們使用這樣的方式來定義一個指針:
Type *p;
我們說p是指向type類型的指針,type可以是任意類型,除了可以是char,short, int, long等基本類型外,還可以是指針類型,例如int *, int **, 或者更多級的指針,也可是是結構體,類或者函數(shù)等。于是,我們說:
int * 是指向int類型的指針;int **,也即(int *) *,是指向int *類型的指針,也就是指向指針的指針;int ***,也即(int **) *,是指向int**類型的指針,也就是指向指針的指針的指針;…我想你應該懂了
struct xxx *,是指向struct xxx類型的指針;
其實,說這么多,只是希望大家在看到指針的時候,不要被int ***這樣的東西嚇到,就像前面說的,指針就是指向某種類型的指針,我們只看最后一個*號,前面的只不過是type類型罷了。
細心一點的人應該發(fā)現(xiàn)了,在“什么是指針”這一小節(jié)當中,已經(jīng)表明了:指針的長度跟CPU的位數(shù)相等,大部分的CPU是32位的,因此我們說,指針的長度是32bit,也就是4個字節(jié)!注意:任意指針的長度都是4個字節(jié),不管是什么指針?。ó斎?4位機自己去測一下,應該是8個字節(jié)吧。。。)
?于是:
Type *p;
sizeof(p)的值是4,Type可以是任意類型,char,int, long, struct, class, int **…
以后大家看到什么sizeof(char*), sizeof(int *),sizeof(xxx *),不要理會,統(tǒng)統(tǒng)寫4,只要是指針,長度就是4個字節(jié),絕對不要被type類型迷惑!?為什么程序中的數(shù)據(jù)會有自己的地址?
弄清這個問題我們需要從操作系統(tǒng)的角度去認知內(nèi)存。
電腦維修師傅眼中的內(nèi)存是這樣的:內(nèi)存在物理上是由一組DRAM芯片組成的。
而作為一個程序員,我們不需要了解內(nèi)存的物理結構,操作系統(tǒng)將RAM等硬件和軟件結合起來,給程序員提供的一種對內(nèi)存使用的抽象。
這種抽象機制使得程序使用的是虛擬存儲器,而不是直接操作和使用真實存在的物理存儲器。
所有的虛擬地址形成的集合就是虛擬地址空間。
在程序員眼中的內(nèi)存應該是下面這樣的。
也就是說,內(nèi)存是一個很大的,線性的字節(jié)數(shù)組(平坦尋址)。每一個字節(jié)都是固定的大小,由8個二進制位組成。
最關鍵的是,每一個字節(jié)都有一個唯一的編號,編號從0開始,一直到最后一個字節(jié)。
如上圖中,這是一個256M的內(nèi)存,他一共有256x1024x1024 ?= 268435456個字節(jié),那么它的地址范圍就是 0 ~268435455 ?。
由于內(nèi)存中的每一個字節(jié)都有一個唯一的編號。
因此,在程序中使用的變量,常量,甚至數(shù)函數(shù)等數(shù)據(jù),當他們被載入到內(nèi)存中后,都有自己唯一的一個編號,這個編號就是這個數(shù)據(jù)的地址。
指針就是這樣形成的。
下面用代碼說明
#include
int main(void)
{
? ?char ch = 'a';
? ?int ?num = 97;
? ?printf("ch 的地址:%p
",