CPU眼裡的:匯編語言

2024年2月6日 25点热度 0人点赞

我們需要學習匯編語言嗎?學多少合適?怎麼學習會比較有效?

01

提出問題

如何有效的學習匯編語言?答案往往取決於你的使用場景。今天匯編語言幾乎退出了常規編程語言的行列。除非你是專門的CPU或芯片開發工程師,一般來說你的日常工作都不會接觸匯編語言。

但如果作C/C 語言編程的話,一點點的匯編語言,卻可能幫你打開一個新世界。

02

代碼分析

打開 Compiler Explorer,寫一個簡單的自加函數,如圖所示:

其中,左下角是CPU的初始狀態,所有的寄存器初始值都是:0x100。

其中寄存器 rax,一般用來存放數值,有點類似 C 語言的普通變量;而寄存器 rbp,rsp,一般用來存放內存地址,有點類似 C 語言的指針變量。

線程往往通過調用函數來運行,因此,必須要有一個“堆棧”,用來存儲:臨時變量和函數返回地址,所以“堆棧”內存是必不可少的。而rbp,rsp寄存器,就是用來管理、讀寫“堆棧”內存的。具體的分析,可以參看“CPU眼裡的{函數括號}”

首先看:函數 { 對應的匯編指令:push,千萬不要被這個熟悉、親切的名字迷惑。這是典型的復雜指令,無數同學被它直接勸退,如圖所示。

push 對應了 2 個微操作:

第一步:先讓“棧頂”向低生長,也就是讓rsp 寄存器的值,減:8,此時寄存器rsp保存的值就是:0xF8;

第二步:隨後把將寄存器 rbp 的值(0x100),存放在“棧頂”寄存器 rsp 指向的內存地址:0xF8處。

隨後是一個簡單的 mov 指令,把寄存器 rsp 的值,賦給寄存器 rbp,如圖所示:

至此,函數的棧幀保護工作完成,更詳細的棧幀工作原理;可以參看“CPU眼裡的{函數括號}”

接著,是一個比較復雜的 mov 指令,如圖所示:

但通過參考源代碼,我們很容易猜出它是要把數值 1,寫入到變量 a 所在的內存。

用於寫入的 mov 指令和數值 1 都很容易找到,但變量 a 的內存地址,就顯得頗為復雜。不過 PTR 關鍵字顯然在提示我們:這是一個指針操作,再加上 rbp 本身就是類似指針變量的寄存器。

所以,它對應的 C 語言,是這樣的:*(rbp - 8) = 1

變量 a 的內存地址,等於:寄存器 rbp 的值減 8;而中括號,就是相當於指針變量的 * 操作;QWORD 是指針類型,表明數值 1 將占用:8 個字節長度。

你是不是也從中看到了:C 語言的影子?所以說:C 語言是最接近底層的高級語言,真的一點都不過分。同樣,相比於精簡指令集,復雜指令集對程序員而言,也更加接近 C 語言。

好了,如果此時,你還能跟上阿佈的節奏,那麼恭喜你!因為,這就是本文中,最難的匯編語言了。後面的學習,將輕松不少。

讓我們接著進行自加運算,如圖所示:

這種帶 PTR 和 [] 的 add 指令,也有 2 個微操作,它們對應的 C 語言是這樣的:*(rbp – 8) = *(rbp – 8) 2

• 首先,用指針的 * 讀操作,獲得變量 a 的值,並與 2 作加法運算;

• 最後,把加法運算的結果,通過指針的 * 寫操作,寫入變量 a 所在的內存。

隨後的 mov 指令,同樣是一個帶 PTR 和 [] 的指令,分析的方法,跟上面的 mov 指令一致,如圖所示:

它對應的 C 語言是這樣的:rax = *(rbp – 8)

隻是不同於上面的 mov 指令,是一個寫內存的操作;這次則是把 a 的值從內存中讀出來,並寫入到寄存器 rax 裡面。

或許,你會納悶:為什麼普通變量操作,背後也弄的跟“指針”一樣?在 CPU 眼裡的,萬物皆有地址,萬物皆可指針。指針變量,跟普通變量並無本質區別,具體可以參看“CPU眼裡的:指針本質和風險”

最後,就是 push 的反向操作:pop,如圖所示:

它也對應了 2 個微操作:

第一步:把寄存器 rsp 指向的“棧頂”值(0x100),寫入到寄存器:rbp;

第二步:隨著“棧頂”的升高,rsp 寄存器的值,也隨之加:8。

至此,整個代碼基本走完,除了用於作返回值的寄存器 rax;所有寄存器,都恢復到了剛開始的狀況,就像 test 函數從未被調用一樣。

03

思考

或許,本章節是本書中,最乏味的一個。因為,在沒有結合編譯器意圖的情況下,單獨討論每條匯編指令,是非常乏味的!

不知道:讀者裡面,有沒有幹過工地的工友?很多精神、漂亮的房子,在真正施工的時候,不過是在重復:搭鋼筋,倒水泥;再搭鋼筋,再倒水泥的重復工作。而 CPU 也是如此,我們不過是把數據,在寄存器和內存之間,搬來搬去。

或許,本章節也是全書中,最具洞察力的一節。經過粗略的統計,我們發現:為了作 1 次簡單的 2 運算,居然產生了(至少) 5 次的內存讀寫,內存讀寫的占比高達:83%!

雖然,經過編譯器優化後,一些沒有必要的內存讀寫指令,會被優化掉。但對於復雜程序,其內存的讀寫總量,仍然不容小覷!有些機構給出的結論顯示:CPU 的內存讀寫,占據了 CPU 90%的工作負荷。

這也是為什麼蘋果的 M 系列 CPU,在沒有顯著提高:CPU 核心頻率的情況下,也能產生:秒殺同行的炸裂性能,因為它著重優化了:CPU 讀、寫內存的效率。

04

總結

1. 雖然完整的 CPU 寄存器和指令集,比較龐大。但編譯器,隻會用到很小的一部分,而且使用的套路也很單一。一旦克服恐懼心理,就很容易掌握。

2. C/C 語言,對應的匯編指令,存在大量的類似 “指針” 的操作,我們也叫它:寄存器間接尋址。誇張的說 “指針” 不僅是 C 語言的靈魂,也是匯編語言的靈魂。

3. 相比於精簡指令集,復雜指令集對程序員而言,更加接近 C 語言。在那個隻有匯編語言的年代,復雜指令集,十分有助於提高編程效率。

最後,作為普通程序員,我們直接使用匯編語言編程的可能性幾乎為 0。在今天,匯編語言,也不是大規模軟件開發的首選。所以,很多時候,我們並不需要成為匯編語言的專傢。

阿佈認為,普通開發者學習匯編語言,最好要結合特定、必要的場景。例如:我們可以用 CPU 視角,解讀出一個真實的程序運行過程;或幫助我們調試、解決一些無法在語言層面表現出來的 bug。

05

熱點問題

Q1:寄存器eax和寄存器rax有什麼區別?

A1:寄存器eax是32位的x86 CPU的寄存器,如今的x86 CPU多是64位的,其對應的寄存器是rax,eax隻是rax的低32位而已。

Q2:不精通匯編語言,等於白學編程語言了?

A2:當然不是。對於學習C/C 這種相對接近底層的語言,它對應的匯編語言還是比較簡單、易懂的,完全不需要你精通匯編。同時,一些新的編程語言,例如:RUST,SWIFT,編譯器對代碼封裝得比較厲害,就不容易通過對應的匯編指令,了解語言的實現細節了。

而且有些語言,例如:Java,JavaScript對應的是字節碼,並沒有匯編指令可以參考,但這並不妨礙大傢掌握它們。

Q3:現在學習8086匯編語言,會不會有點老?

A3:這取決於我們的學習目的,如果是為了學習一門編程語言的話,8086是夠用的。不過阿佈更願意結合目前最新、最主流的CPU來學習匯編語言,這不僅僅是因為更接近目前真實的計算場景,更重要的是,C/C 等主流編程語言,也往往是基於32/64位CPU設計的,我們能夠在學習匯編語言的過程中,順便解讀C/C 語言的設計原理和工作細節,豈不是一舉多得?

06

更多知識

如果喜歡阿佈這種解讀方式,希望更加系統學習這些編程知識的話,也可以考慮看看由阿佈親自編寫,並由多位微軟大佬聯袂推薦的新書《CPU眼裡的C/C 》

#pgc-card .pgc-card-href { text-decoration: none; outline: none; display: block; width: 100%; height: 100%; } #pgc-card .pgc-card-href:hover { text-decoration: none; } /*pc 樣式*/ .pgc-card { box-sizing: border-box; height: 164px; border: 1px solid #e8e8e8; position: relative; padding: 20px 94px 12px 180px; overflow: hidden; } .pgc-card::after { content: " "; display: block; border-left: 1px solid #e8e8e8; height: 120px; position: absolute; right: 76px; top: 20px; } .pgc-cover { position: absolute; width: 162px; height: 162px; top: 0; left: 0; background-size: cover; } .pgc-content { overflow: hidden; position: relative; top: 50%; -webkit-transform: translateY(-50%); transform: translateY(-50%); } .pgc-content-title { font-size: 18px; color: #222; line-height: 1; font-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .pgc-content-desc { font-size: 14px; color: #444; overflow: hidden; text-overflow: ellipsis; padding-top: 9px; overflow: hidden; line-height: 1.2em; display: -webkit-inline-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .pgc-content-price { font-size: 22px; color: #f85959; padding-top: 18px; line-height: 1em; } .pgc-card-buy { width: 75px; position: absolute; right: 0; top: 50px; color: #406599; font-size: 14px; text-align: center; } .pgc-buy-text { padding-top: 10px; } .pgc-icon-buy { height: 23px; width: 20px; display: inline-block; background: url(https://lf6-cdn-tos.bytescm.com/obj/cdn-static-resource/pgc/v2/pgc_tpl/static/image/commodity_buy_f2b4d1a.png); }