剛剛接觸C語言,網課的老師一直在課程中提到彙編語言,一臉懵逼,我應該去學彙編嗎?


先學好C語言再說吧,學了彙編能更好的理解底層的一些操作


C語言只是個工具,看你要拿它做什麼?

看下自己專業和今後從事什麼工作,計算機專業或者和硬體有關係的話,建議瞭解學習下彙編


如果要深入進行系統級開發

彙編是必學的

如果只是過度 要學其他高級語言比如 jpg

那 無所謂


學習C語言不需要學習彙編

網課老師在故弄玄虛蒙你們


學習編程其實就是學高級語言,即那些為人類設計的計算機語言。

但是,計算機不理解高級語言,必須通過編譯器轉成二進位代碼,才能運行。學會高級語言,並不等於理解計算機實際的運行步驟。

計算機真正能夠理解的是低級語言,它專門用來控制硬體。彙編語言就是低級語言,直接描述/控制 CPU 的運行。如果你想了解 CPU 到底幹了些什麼,以及代碼的運行步驟,就一定要學習彙編語言。

彙編語言不容易學習,就連簡明扼要的介紹都很難找到。下面我嘗試寫一篇最好懂的彙編語言教程,解釋 CPU 如何執行代碼。

彙編語言是什麼

我們知道,CPU 只負責計算,本身不具備智能。你輸入一條指令(instruction),它就運行一次,然後停下來,等待下一條指令。

這些指令都是二進位的,稱為操作碼(opcode),比如加法指令就是00000011。編譯器的作用,就是將高級語言寫好的程序,翻譯成一條條操作碼。

對於人類來說,二進位程序太難閱讀了,通過代碼根本看不出來機器幹了什麼。為瞭解決可讀性的問題,以及偶爾的編輯需求,就誕生了彙編語言。

彙編語言是二進位指令的文本形式,與二進位指令是一一對應的關係。比如,加法指令00000011寫成彙編語言就是 ADD。只要還原成二進位,彙編語言就可以被 CPU 直接執行,所以它是最底層的低級語言。

來歷

最早的時候,編寫程序就是手寫二進位指令,然後通過各種開關輸入計算機,比如要做加法了,就按一下加法開關。後來,發明瞭紙帶打孔機,通過在紙帶上打孔,將二進位指令自動輸入計算機。

為瞭解決二進位指令的可讀性問題,工程師將那些指令寫成了八進位。二進位轉八進位是輕而易舉的,但是八進位的可讀性也不行。很自然地,最後還是用文字表達,加法指令寫成 ADD。內存地址也不再直接引用,而是用標籤表示。

這樣的話,就多出一個步驟,要把這些文字指令翻譯成二進位,這個步驟就稱為 assembling,完成這個步驟的程序就叫做 assembler。它處理的文本,自然就叫做 aseembly code。標準化以後,稱為 assembly language,縮寫為 asm,中文譯為彙編語言。

每一種 CPU 的機器指令都是不一樣的,因此對應的彙編語言也不一樣。本文介紹的是目前最常見的 x86 彙編語言,即 Intel 公司的 CPU 使用的那一種。

寄存器

學習彙編語言,首先必須瞭解兩個知識點:寄存器和內存模型。

先來看寄存器。CPU 本身只負責運算,不負責儲存數據。數據一般都儲存在內存之中,CPU 要用的時候就去內存讀寫數據。但是,CPU 的運算速度遠高於內存的讀寫速度,為了避免被拖慢,CPU 都自帶一級緩存和二級緩存。基本上,CPU 緩存可以看作是讀寫速度較快的內存。

但是,CPU 緩存還是不夠快,另外數據在緩存裡面的地址是不固定的,CPU 每次讀寫都要定址也會拖慢速度。因此,除了緩存之外,CPU 還自帶了寄存器(register),用來儲存最常用的數據。也就是說,那些最頻繁讀寫的數據(比如循環變數),都會放在寄存器裡面,CPU 優先讀寫寄存器,再由寄存器跟內存交換數據。

寄存器不依靠地址區分數據,而依靠名稱。每一個寄存器都有自己的名稱,我們告訴 CPU 去具體的哪一個寄存器拿數據,這樣的速度是最快的。有人比喻寄存器是 CPU 的零級緩存。

寄存器種類

早期的 x86 CPU 只有8個寄存器,而且每個都有不同的用途。現在的寄存器已經有100多個了,都變成通用寄存器,不特別指定用途了,但是早期寄存器的名字都被保存了下來。

EAX
EBX
ECX
EDX
EDI
ESI
EBP
ESP

上面這8個寄存器之中,前面七個都是通用的。ESP 寄存器有特定用途,保存當前 Stack 的地址。

我們常常看到 32位 CPU、64位 CPU 這樣的名稱,其實指的就是寄存器的大小。32 位 CPU 的寄存器大小就是4個位元組。

內存模型:Heap

寄存器只能存放很少量的數據,大多數時候,CPU 要指揮寄存器,直接跟內存交換數據。所以,除了寄存器,還必須瞭解內存怎麼儲存數據。

程序運行的時候,操作系統會給它分配一段內存,用來儲存程序和運行產生的數據。這段內存有起始地址和結束地址,比如從0x1000到0x8000,起始地址是較小的那個地址,結束地址是較大的那個地址。

程序運行過程中,對於動態的內存佔用請求(比如新建對象,或者使用malloc命令),系統就會從預先分配好的那段內存之中,劃出一部分給用戶,具體規則是從起始地址開始劃分(實際上,起始地址會有一段靜態數據,這裡忽略)。舉例來說,用戶要求得到10個位元組內存,那麼從起始地址0x1000開始給他分配,一直分配到地址0x100A,如果再要求得到22個位元組,那麼就分配到0x1020。

這種因為用戶主動請求而劃分出來的內存區域,叫做 Heap(堆)。它由起始地址開始,從低位(地址)向高位(地址)增長。Heap 的一個重要特點就是不會自動消失,必須手動釋放,或者由垃圾回收機制來回收。

內存模型:stack

除了 Heap 以外,其他的內存佔用叫做 Stack(棧)。簡單說,Stack 是由於函數運行而臨時佔用的內存區域。

請看下面的例子。

int main() {
int a = 2;
int b = 3;
}

上面代碼中,系統開始執行main函數時,會為它在內存裡面建立一個幀(frame),所有main的內部變數(比如a和b)都保存在這個幀裡面。main函數執行結束後,該幀就會被回收,釋放所有的內部變數,不再佔用空間。

如果函數內部調用了其他函數,會發生什麼情況?

int main() {
int a = 2;
int b = 3;
return add_a_and_b(a, b);
}

上面代碼中,main函數內部調用了add_a_and_b函數。執行到這一行的時候,系統也會為add_a_and_b新建一個幀,用來儲存它的內部變數。也就是說,此時同時存在兩個幀:main和add_a_and_b。一般來說,調用棧有多少層,就有多少幀。

等到add_a_and_b運行結束,它的幀就會被回收,系統會回到函數main剛才中斷執行的地方,繼續往下執行。通過這種機制,就實現了函數的層層調用,並且每一層都能使用自己的本地變數。

所有的幀都存放在 Stack,由於幀是一層層疊加的,所以 Stack 叫做棧。生成新的幀,叫做"入棧",英文是 push;棧的回收叫做"出棧",英文是 pop。Stack 的特點就是,最晚入棧的幀最早出棧(因為最內層的函數調用,最先結束運行),這就叫做"後進先出"的數據結構。每一次函數執行結束,就自動釋放一個幀,所有函數執行結束,整個 Stack 就都釋放了。

Stack 是由內存區域的結束地址開始,從高位(地址)向低位(地址)分配。比如,內存區域的結束地址是0x8000,第一幀假定是16位元組,那麼下一次分配的地址就會從0x7FF0開始;第二幀假定需要64位元組,那麼地址就會移動到0x7FB0。

CPU指令

一個實例

瞭解寄存器和內存模型以後,就可以來看彙編語言到底是什麼了。下面是一個簡單的程序example.c。

int add_a_and_b(int a, int b) {
return a + b;
}
int main() {
return add_a_and_b(2, 3);
}

gcc 將這個程序轉成彙編語言。

$ gcc -S example.c

上面的命令執行以後,會生成一個文本文件example.s,裡面就是彙編語言,包含了幾十行指令。這麼說吧,一個高級語言的簡單操作,底層可能由幾個,甚至幾十個 CPU 指令構成。CPU 依次執行這些指令,完成這一步操作。

example.s經過簡化以後,大概是下面的樣子。

_add_a_and_b:
push %ebx
mov %eax, [%esp+8]
mov %ebx, [%esp+12]
add %eax, %ebx
pop %ebx
ret

_main:
push 3
push 2
call _add_a_and_b
add %esp, 8
ret

可以看到,原程序的兩個函數add_a_and_b和main,對應兩個標籤_add_a_and_b和_main。每個標籤裡面是該函數所轉成的 CPU 運行流程。

每一行就是 CPU 執行的一次操作。它又分成兩部分,就以其中一行為例。

push %ebx

這一行裡面,push是 CPU 指令,%ebx是該指令要用到的運運算元。一個 CPU 指令可以有零個到多個運運算元。

下面我就一行一行講解這個彙編程序,建議讀者最好把這個程序,在另一個窗口拷貝一份,省得閱讀的時候再把頁面滾動上來。

push 指令

根據約定,程序從_main標籤開始執行,這時會在 Stack 上為main建立一個幀,並將 Stack 所指向的地址,寫入 ESP 寄存器。後面如果有數據要寫入main這個幀,就會寫在 ESP 寄存器所保存的地址。

然後,開始執行第一行代碼。

push 3

push指令用於將運運算元放入 Stack,這裡就是將3寫入main這個幀。

雖然看上去很簡單,push指令其實有一個前置操作。它會先取出 ESP 寄存器裡面的地址,將其減去4個位元組,然後將新地址寫入 ESP 寄存器。使用減法是因為 Stack 從高位向低位發展,4個位元組則是因為3的類型是int,佔用4個位元組。得到新地址以後, 3 就會寫入這個地址開始的四個位元組。

push 2

第二行也是一樣,push指令將2寫入main這個幀,位置緊貼著前面寫入的3。這時,ESP 寄存器會再減去 4個位元組(累計減去8)。

call 指令

第三行的call指令用來調用函數。

call _add_a_and_b

上面的代碼表示調用add_a_and_b函數。這時,程序就會去找_add_a_and_b標籤,並為該函數建立一個新的幀。

下面就開始執行_add_a_and_b的代碼。

push %ebx

這一行表示將 EBX 寄存器裡面的值,寫入_add_a_and_b這個幀。這是因為後面要用到這個寄存器,就先把裡面的值取出來,用完後再寫回去。

這時,push指令會再將 ESP 寄存器裡面的地址減去4個位元組(累計減去12)。

mov 指令

mov指令用於將一個值寫入某個寄存器。

mov %eax, [%esp+8]

這一行代碼表示,先將 ESP 寄存器裡面的地址加上8個位元組,得到一個新的地址,然後按照這個地址在 Stack 取出數據。根據前面的步驟,可以推算出這裡取出的是2,再將2寫入 EAX 寄存器。

下一行代碼也是幹同樣的事情。

mov %ebx, [%esp+12]

上面的代碼將 ESP 寄存器的值加12個位元組,再按照這個地址在 Stack 取出數據,這次取出的是3,將其寫入 EBX 寄存器。

add 指令

add指令用於將兩個運運算元相加,並將結果寫入第一個運運算元。

add %eax, %ebx

上面的代碼將 EAX 寄存器的值(即2)加上 EBX 寄存器的值(即3),得到結果5,再將這個結果寫入第一個運運算元 EAX 寄存器。

pop 指令

pop指令用於取出 Stack 最近一個寫入的值(即最低位地址的值),並將這個值寫入運運算元指定的位置。

pop %ebx

上面的代碼表示,取出 Stack 最近寫入的值(即 EBX 寄存器的原始值),再將這個值寫回 EBX 寄存器(因為加法已經做完了,EBX 寄存器用不到了)。

注意,pop指令還會將 ESP 寄存器裡面的地址加4,即回收4個位元組。

ret 指令

ret指令用於終止當前函數的執行,將運行權交還給上層函數。也就是,當前函數的幀將被回收。ret

可以看到,該指令沒有運運算元。

隨著add_a_and_b函數終止執行,系統就回到剛才main函數中斷的地方,繼續往下執行。

add %esp, 8

上面的代碼表示,將 ESP 寄存器裡面的地址,手動加上8個位元組,再寫回 ESP 寄存器。這是因為 ESP 寄存器的是 Stack 的寫入開始地址,前面的pop操作已經回收了4個位元組,這裡再回收8個位元組,等於全部回收。

ret

最後,main函數運行結束,ret指令退出程序執行。

-THE END-

百問網技術論壇:

http://bbs.100ask.net/

百問網官方wiki(資料下載):

http://wiki.100ask.org/

線上課堂:

https://www.100ask.net/index

百問網開發板:

淘寶:https://100ask.taobao.com/天貓:https://weidongshan.tmall.com/

技術交流羣(鴻蒙開發/Linux/嵌入式/驅動/資料下載)

QQ羣:869222007

單片機-嵌入式Linux交流羣:

QQ羣:536785813

http://weixin.qq.com/r/M0hvd2-EwjK9rSsU9x0h (二維碼自動識別)

百問科技公眾號


學習彙編語言看個人,要深入理解指針,最好了解下彙編語言。這樣對於其他語言你也能迅速掌握原理。


c語言要學精至少得適當瞭解彙編,知道cpu實際的計算流程才能理解c語言裏一些有別於其他高級語言的奇怪設計。

另一方面,現代cpu的指令也有不少針對c語言的優化設計。


打個比方吧,編程中的應用場景就像我們寫作文。彙編是拼音,C語言是漢字+造句。

如果你以後要靠編程這個喫飯,建議學彙編,不學彙編你就不會懂寄存器,更不要談什麼CPU緩存等高性能編程;而學會彙編,那就能自己造精度比較高的輪子。而拼音學不好,是不太好自己通過查字典學生僻字的。

如果你不靠編程喫飯,就直接學C語言吧。我小時候見過挺多同學不會太拼音,但後來說話認字也沒太大問題。但如果遇到生僻字需要查字典,他可能就犯難了。


不用吧,對於大多數學科來說,學C語言只是學一個處理重複操作的工具,把C這一個工具搞懂怎麼用就行了,沒必要再去追究彙編語言是怎麼工作的。


不需要學彙編

但要學習《計算機組成原理》


不需要。學習語言分兩個層次,首先是語言本身,這時候聚焦在語法,語言特性和程序邏輯上就夠了;進一步學習擴展到相關領域,比如計算機基礎知識,編譯原理,演算法,設計模式,其他語言等等。


彙編只是讓你更加理解操作系統底層,如果想深入探究計算機底層,可以去了解,但是不建議深入去學習,畢竟現在彙編已經在市場上寥寥無幾le,希望對你有所幫助!

回答者:微信公眾號《後臺伺服器開發》小編


用不到,內聯彙編幾乎不可能用到


不用學。

老師應該是在裝逼。


學習彙編有助於學習C語言。

彙編主要用於硬體底層操作和高效率操作時。


需要學習下,學彙編會讓你更懂硬體


不用,高級語言出現就是為了屏蔽底層實現,讓開發者更關注問題,解決實際問題。如果要研究底層原理和程序是怎麼運行的,需要學習下。一般


先學c,學到指針那之後去學彙編,彙編學完,再來接著學指針


學習C語言不一定需要學習彙編,但是想成為高手必須學習彙編,C語言主要用來實現功能開發,但是對於計算機運行原理和過程,通過彙編語言能夠有更清晰的瞭解,學習C語言初期不用糾結有沒有學習過彙編,沒有學習過彙編也可以把C語言學好


那肯定不用啊。你學簡體中文還得先會小篆麼?


不一定,但想深入學習就需要了


未必哦,不學習彙編也可以


推薦閱讀:
相關文章