從計(jì)算機(jī)電路來理解指針

我學(xué)習(xí) C++ 遇到的第一個(gè)難以理解的點(diǎn)就是指針,一級(jí)指針多級(jí)指針搞得暈頭轉(zhuǎn)向,為了理解指針,早些年看了很多文章也看過許多視頻。當(dāng)時(shí)我都是這么類比的:變量就是我的名字,指針就是我的住址,引用就是我的別名,二級(jí)指針是住址的住址,三級(jí)指針那就是住址的住址的住址,再往后以此往下類推。

1. 類比法理解

int main() {
    // 定義變量
    int number = 1;
    // 獲取指針
    int* p_number = &number;
    // 通過指針操作變量賦值
    *p_number = 2;
    // cout << "number = " << number << endl;
    return 0;
}

以上是一個(gè)最簡(jiǎn)單的代碼,number 就是變量的名字,p_number 就是通過變量拿到了住址,*p_number 通過住址找到變量,可以間接的操作變量賦值 。這樣理解幾乎能解決所有開發(fā)中遇到的問題,除非特別復(fù)雜奇怪的場(chǎng)景。那后面 new 對(duì)象出來的指針呢?其實(shí)本質(zhì)上也都是一樣的。

2. 從計(jì)算機(jī)電路來理解指針

所謂類比,只是方便大家記憶,其實(shí)就是說完全不是這么一回事。在計(jì)算機(jī)看來,變量、指針、住址、別名等等這些,都是不存在的,唯一存在的就是內(nèi)存高低電壓(地址)。接下來,我們?cè)囍鴱挠?jì)算機(jī)電路的角度來理解指針。

匯編結(jié)果.png

這圖是我們上面的代碼編譯出來的結(jié)果,紅色標(biāo)記的是 cpu 最終要執(zhí)行的機(jī)器碼指令,這里是以 16 進(jìn)制來呈現(xiàn)的,最終編譯出來是 101101...... 這樣。藍(lán)色標(biāo)記(希望我沒色盲)的是匯編指令,這個(gè)是編譯的中間產(chǎn)物代碼,為了方便我們理解所以我把匯編指令也放出來了。匯編和機(jī)器碼指令這些不在本文詳細(xì)解釋,不是我們本文的重點(diǎn),后面我會(huì)陸續(xù)寫一些文章。寫文章實(shí)在是太耗時(shí)間了,一篇文章幾乎花掉我一周的業(yè)余時(shí)間,但視頻我可能 10 分鐘就能講清楚。這里我簡(jiǎn)單用文字解釋,大家感興趣可以去這個(gè)網(wǎng)站實(shí)踐代碼:compiler explorer

int number = 1; 
// 對(duì)應(yīng)如下
mov    DWORD PTR [rbp-0xc],0x1

int* p_number = &number;
// 對(duì)應(yīng)如下
lea    rax,[rbp-0xc]
mov    QWORD PTR [rbp-0x8],rax

*p_number = 2;
// 對(duì)應(yīng)如下
mov    rax,QWORD PTR [rbp-0x8]
mov    DWORD PTR [rax],0x2
  • mov DWORD PTR [rbp-0xc],0x1:cpu 讀到這條指令將向內(nèi)存地址 [rbp-0xc] 寫入 0x1
  • lea rax,[rbp-0xc]:cpu 讀到這條指令將 [rbp-0xc] 的地址值賦值給 rax 寄存器
  • mov QWORD PTR [rbp-0x8],rax:cpu 讀到這條指令將 rax 寄存器的值寫入 [rbp-0x8] 內(nèi)存地址
  • mov rax,QWORD PTR [rbp-0x8]:cpu 讀到這條指令將 [rbp-0x8] 的值賦值給寄存器 rax
  • mov DWORD PTR [rax],0x2:cpu 讀到這條指令將向 rax 內(nèi)存地址上存的值當(dāng)作目標(biāo)地址,寫入 0x2,注意這里的 rax 用 [ ] 圍起來了

2.1 cup 操作內(nèi)存地址

cpu 操作內(nèi)存簡(jiǎn)圖.png
  • cpu 通過地址總線可以選擇操作內(nèi)存條上的某個(gè)位置,早期 cpu 只有 20 根地址總線內(nèi)存只有 1M,現(xiàn)在一般都有 32 根地址線那么就可以達(dá)到 4GB。這個(gè)就是我們本文所說的內(nèi)存地址
  • cpu 通過讀寫操作線可以選擇是讀入數(shù)據(jù)還是寫入數(shù)據(jù)
  • cpu 通過數(shù)據(jù)總線把數(shù)據(jù)寫入內(nèi)存條,也可以從內(nèi)存中把數(shù)據(jù)讀入寄存器,32 根線代表我們一次能讀取 32 位數(shù)據(jù),64 根代表我們一次能讀取 64 位數(shù)據(jù),也就是我們常說的 32 位計(jì)算機(jī)與 64 位計(jì)算機(jī)

現(xiàn)在我們可以來嘗試著翻譯一下 cup 是如何操作內(nèi)存地址的了,注意此處省略 cpu 讀取指令的操作等等細(xì)節(jié),對(duì)應(yīng)代碼如下:

int* p_number = &number;

翻譯成匯編代碼如下:

lea    rax,[rbp-0xc]
mov    QWORD PTR [rbp-0x8],rax

翻譯成 cpu 機(jī)器指令如下:

01001000100011010100100111110100
01001000100010010100100111111000

假設(shè)我們現(xiàn)在 rbp 寄存器的值存的是 0x0f0f0f0d(32位計(jì)算機(jī))第一條指令不需要操作內(nèi)存,把寄存器 rbp 的值減掉 12 存到 rax 寄存器上,執(zhí)行完第一條指令后,寄存器 rax = rbp - 12 = 0x0f0f0f01。第二條指令需要操作內(nèi)存,把 rax(0x0f0f0f01) 的值寫入到 0x0f0f0f0f5 的內(nèi)存地址上。將上圖的讀寫操作線設(shè)置為寫,往讀寫操作線上傳輸高低電壓來控制讀寫。往地址總線上傳輸高低電壓來選擇地址,32 根地址總線上傳輸高低電壓為 00001111000011110000111100001001(0x0f0f0f0f5)往 32 根數(shù)據(jù)線上傳輸高低電壓為 00001111000011110000111100000001(0x0f0f0f01)執(zhí)行完當(dāng)前指令后,以此類推再去讀取下一條指令,繼續(xù)執(zhí)行代碼。

2.2 cup 內(nèi)部構(gòu)造

cpu 芯片的內(nèi)部構(gòu)造.png
  • 寄存器:像上面的 rax、rbp 代表的都是 cpu 內(nèi)部的寄存器,這些電路用來臨時(shí)存放數(shù)據(jù),有記憶功能
  • 計(jì)數(shù)器:從哪里開始取指令,取完第一條指令執(zhí)行完就要去取下一條指令,有計(jì)數(shù)功能
  • 計(jì)算器:有些指令需要做加減乘除,有計(jì)算功能
  • 控制器:讀取指令譯碼,操作執(zhí)行指令,控制內(nèi)存、IO 等,有控制功能

無論多復(fù)雜的 CPU 內(nèi)部都是由電子元器件(主要是晶體管)組成的數(shù)字電路,寄存器電路最簡(jiǎn)單,控制器電路最復(fù)雜。這些不在本文的范圍內(nèi),我有打算后面錄制一些科普視頻,從電子元器件 -> 數(shù)字電路(門電路) -> CPU 內(nèi)部的詳細(xì)電路。關(guān)于 CPU 芯片卡脖子的制造工藝和歷史,我推薦我們 WXG 的微信讀書 App 里面的《芯片戰(zhàn)爭(zhēng)》

3. 最后雜談

高級(jí)語言 -> 匯編語言 -> cpu工作原理 -> 數(shù)字電路,再加上數(shù)據(jù)結(jié)構(gòu)算法、編譯原理、操作系統(tǒng)和虛擬機(jī)。這個(gè)是我的一個(gè)學(xué)習(xí)路徑,僅供大家參考。知識(shí)浩瀚無限,人的精力和時(shí)間卻有限,僅僅是一個(gè) linux 操作系統(tǒng)就有上千萬行代碼,所以我也是一無所知。原則上我們需要盡可能熟悉我們工作的下一層原理,比如以前我做 Android 的時(shí)候會(huì)花盡可能多的精力去閱讀 Framework 的源碼,所以之前對(duì)于 Android 的分享到 Framework 就打止了。我現(xiàn)在做 iOS 和 C++ 需要了解的就更多了一些。

現(xiàn)代計(jì)算機(jī)很復(fù)雜,我上面畫的圖非常簡(jiǎn)單,比如 CPU 內(nèi)部現(xiàn)在一般都是多核、指令執(zhí)行有指令流水線、cpu 內(nèi)部還有多級(jí)的快速緩存。我們移動(dòng)端開發(fā)的應(yīng)用都是運(yùn)行在操作系統(tǒng)上的,對(duì)于我們應(yīng)用層來說都是虛擬地址,操作系統(tǒng)加上硬件一起配合才會(huì)轉(zhuǎn)成真實(shí)的物理地址。早期的計(jì)算機(jī)并沒有這么復(fù)雜,更有利于大家學(xué)習(xí),所以推薦大家先去了解本質(zhì),從簡(jiǎn)單上手再到復(fù)雜。

有些語言看似很復(fù)雜,但只要是跑在計(jì)算機(jī)上,那么本質(zhì)都是一樣的。匯編、c/c++、OC、swift 等等只是語法可能有點(diǎn)不一樣,但是最終本質(zhì)大家都是一樣的。有些語言依賴虛擬機(jī)比如 Java 語言,但 Java/Android 虛擬機(jī)是 C/C++ 參雜匯編寫的。有些語言依賴解釋器比如 python,解釋器也 是 C/C++ 參雜匯編寫的。學(xué)習(xí)一門語言的語法其實(shí)不用花太多時(shí)間,我記得之前從 Android 轉(zhuǎn)到 iOS 開發(fā)也就用了一周的時(shí)間,關(guān)鍵還是我們對(duì)于底層原理的熟悉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容