指針是怎樣一步步發(fā)明出來的?
大家好,我是小風(fēng)哥,今天來聊聊指針是怎么被一步步被發(fā)明出來的。
內(nèi)存本身就是一個裝字節(jié)的容器,和你用的鞋柜、書柜等沒有本質(zhì)區(qū)別:
圖片
唯一的區(qū)別在于鞋子或者書可以隨便放,無非找的時(shí)候困難點(diǎn)。
但字節(jié)就不一樣了,不能隨便放,必須明確放到了哪里,因此內(nèi)存中的每個裝字節(jié)的地方都得編號,這個編號就是內(nèi)存地址。
圖片
在這種情況下,該怎么向內(nèi)存中寫數(shù)據(jù)呢?很簡單,就一句話:
把數(shù)字2寫到第0x8049320號內(nèi)存中
這就是所謂的內(nèi)存讀,用機(jī)器指令表示可以這樣:
store 0x8049320 2
效果是這樣的:
圖片
可以看到,利用store指令你可以直接操作任何一個內(nèi)存地址,也就是直接操作或者控制內(nèi)存這種硬件,這是一種很強(qiáng)大的能力:
圖片
但同時(shí)也非常危險(xiǎn),如果內(nèi)存地址算錯那么寫到內(nèi)存中的數(shù)據(jù)就是錯誤的或者會用錯誤的數(shù)據(jù)覆蓋掉內(nèi)存中原本的數(shù)據(jù):
圖片
而且這也很繁瑣,因?yàn)槌绦騿T需要直面內(nèi)存,看下內(nèi)存地址0x8049320,你的第一反應(yīng)肯定是:這是個啥?
圖片
人類天生不擅長應(yīng)對數(shù)字,而是更喜歡代號:張三李四。
顯然,"把num賦值為2"要比"store 0x8049320 2"要容易理解很多,這里num這個代號就是所謂編程語言中的變量。
圖片
就這樣變量誕生了。
實(shí)際上變量不過是某個內(nèi)存格子的一個代號:
圖片
當(dāng)然這是在編程語言層面的理解,在真實(shí)的內(nèi)存中可不存在一個叫做num的符號,而是內(nèi)存地址0x8049320這個地方保存了數(shù)字2:
圖片
編譯器不過是把代號num和0x8049320這塊內(nèi)存關(guān)聯(lián)起來。
有了變量,程序員在編程時(shí)就可以操作符號num而不是0x8049320了,但只使用符號num也會有問題,這個問題就是如果兩個函數(shù)需要共享內(nèi)存中的一份數(shù)據(jù)該怎么辦呢?
圖片
以C語言為例,現(xiàn)在有兩個函數(shù)都需要對變量num執(zhí)行加1操作:
void func1(int a) {
a = a + 1;
}
void func2(int b) {
b = b + 1;
}
int num = 2;
func1(num);
func2(num);
我們期待的效果是func1和func2執(zhí)行完畢后num的值變成4,但實(shí)際上兩個函數(shù)執(zhí)行完畢后num的值依然是2。
為什么呢?
我們希望的是a和num代表同一個內(nèi)存格子:
圖片
但實(shí)際上變量a上有獨(dú)屬于自己的內(nèi)存格子:注意看函數(shù)的參數(shù)int a,
圖片
調(diào)用函數(shù)傳遞參數(shù)func1(num)后的效果是這樣的:
圖片
func函數(shù)操作的根本就是和num完全不同的另一個變量,它們位于不同的內(nèi)存格子(內(nèi)存地址)。
既然聲明變量時(shí)沒有辦法直接關(guān)聯(lián)到某塊內(nèi)存那么我們就必須用間接的辦法,因?yàn)橛?jì)算機(jī)科學(xué)中任何問題都可以通過增加一個中間層來解決。
圖片
這個中間層就是借助內(nèi)存地址。
圖片
不要忘了除了2關(guān)聯(lián)到了符號num,這當(dāng)然只是邏輯上存在的關(guān)聯(lián),編譯器給實(shí)現(xiàn)的;
實(shí)際上2還有一個真實(shí)的、物理的上的屬性,那就是內(nèi)存地址,這是真實(shí)的存在,不以任何上層封裝為轉(zhuǎn)移;
圖片
既然變量a沒辦法直接關(guān)聯(lián)到num,那就曲線救國,變量a保存2所在的內(nèi)存地址,也就是變量num的內(nèi)存地址:
圖片
變量a依然是那個變量a,但此時(shí)變量a中保存的不再是2這個數(shù)字,而是另一個數(shù)字0x8049320。
然而此時(shí)如果你這樣寫:
int b = a;
此時(shí)b中保存的依然是0x8049320這個數(shù)字,而不是2這個數(shù)字:
圖片
顯然必須明確的告訴編譯器我們希望把變量a的內(nèi)容當(dāng)做內(nèi)存地址來使用而不是單純的數(shù)字。
怎么做到呢?在聲明變量和使用變量時(shí)加個符號就好:
int a; ----> int* a;
int b = a; ----> int b = *a;
就這樣指針被發(fā)明了出來,現(xiàn)在的變量a就是所謂的指針,變量a關(guān)聯(lián)的內(nèi)存保存的依然是個普通的數(shù)字,只不過這個數(shù)字可以被當(dāng)做內(nèi)存地址使用。
再次強(qiáng)調(diào),當(dāng)我們寫下int* a時(shí),變量a依然會占據(jù)一塊內(nèi)存格子:
圖片
這塊內(nèi)存中可以裝入任何的數(shù)字,這個數(shù)字代表的另一塊內(nèi)存的起始地址:
圖片
所以并不是說變量a直接指向一塊內(nèi)存或者指向num:
圖片
變量a和變量num沒有半毛錢關(guān)系,變量a和變量num位于不同的內(nèi)存地址上,只不過變量a的內(nèi)容比較特殊而已,它恰好是變量num所在的內(nèi)存地址:
圖片
所以從這里看我們只能說a間接指向了變量num。
當(dāng)然在你熟悉指針的概念后就可以放心的忽略這層間接了,可以把a(bǔ)看做直接指向變量num,這就是我們常說的指針指向哪里。
圖片
在很多情況下,我們實(shí)際上根本就不關(guān)心內(nèi)存地址這種間接層,可以直接把a(bǔ)看做num的另一個稱謂,這在其它高級語言中叫做引用。
所以引用實(shí)際上是在指針基礎(chǔ)上的進(jìn)一步抽象,使用引用時(shí)我們可以簡單的把a(bǔ)和num等同看待:
圖片
此時(shí)a和num都表示0x8049320這塊內(nèi)存中的內(nèi)容,也就是數(shù)字2。
C語言中的指針把內(nèi)存地址暴露給了程序員,這給了程序員直接控制硬件的能力,這種能力十分的powerful,因此C很適合進(jìn)行系統(tǒng)編程,可以用來實(shí)現(xiàn)操作系統(tǒng)等;
圖片
但也非常危險(xiǎn),內(nèi)存地址計(jì)算錯誤的話會導(dǎo)致程序崩潰或者出現(xiàn)難以排查的bug。
圖片
但并不是所有程序員都要像Linus那樣去編寫操作系統(tǒng),如果你只想實(shí)現(xiàn)一些應(yīng)用層面的程序,爬蟲等,在這種情況下指針就不是必須的,所以很多編程語言并不提供指針。
指針的出現(xiàn)讓高級語言也可以操作復(fù)雜的數(shù)據(jù)結(jié)構(gòu)比如鏈表和二叉樹等。
圖片
1964年Harold Lawson因在PL/I中發(fā)明指針這一概念而榮獲2000年IEEE計(jì)算機(jī)先鋒獎,獲獎理由是“指針概念的引入首次使高級語言靈活處理鏈表成為可能”。