九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
《匯編語言》學(xué)習(xí)筆記

by 張悠慧教授(清華大學(xué)),課程鏈接 https://www.bilibili.com/video/av27895807/?p=1 ,大概有十幾個(gè)小時(shí)的視頻??赐暾n程之后我又回看了阮一峰老師的《匯編語言入門》博客 http://www.ruanyifeng.com/blog/2018/01/assembly-language-primer.html 。因此本筆記就依據(jù)這兩份資料來總結(jié)編寫。

另外,我覺得學(xué)習(xí)匯編語言之前最好先了解 計(jì)算機(jī)組成 的相關(guān)知識(shí),否則遇到一些 CPU 寄存器 內(nèi)存尋址 等相關(guān)概念時(shí),可能會(huì)聽著有點(diǎn)懵。

前言

學(xué)完 計(jì)算機(jī)組成原理 之后接下來再學(xué)什么?通過本課程一開始的圖,就知道要緊接著學(xué)習(xí)匯編語言(再往下是編譯原理、操作系統(tǒng))。

本課程內(nèi)容太多我沒有看完,大概看了 2/3 吧,但這并不影響我來做這個(gè)總結(jié)記錄,因?yàn)槲也皇菍I(yè)搞匯編的,就來了解一下。

  • 本課程主要講解 x86 架構(gòu)的匯編,最后又講 MIPS 匯編,而 MIPS 匯編部分我沒有看
  • 課程中一些演示、講解匯編代碼的細(xì)節(jié)部分我沒有詳細(xì)看,即沒有深入?yún)R編語言的具體指令和參數(shù)

PS:雖然本課程十幾個(gè)小時(shí),看著不長,但是老師語速非??欤?x 速度聽都感覺講話挺快的,因此不敢 1.5x 看。

學(xué)到了什么

這個(gè)問題其實(shí)可以拆解成兩個(gè)問題:第一,匯編語言是什么?第二,高級(jí)語言程序猿學(xué)習(xí)匯編有何用?分開解答。

匯編語言是什么

簡答來說,匯編語言就是機(jī)器語言(二進(jìn)制代碼)的助記符,每條匯編語言都能直接翻譯成機(jī)器語言,如下圖。計(jì)算機(jī)就是一臺(tái)各種電子設(shè)備組成的機(jī)器,它只能識(shí)別機(jī)器代碼,即一堆二進(jìn)制數(shù)字。但是二進(jìn)制不易于人類閱讀,而且在計(jì)算機(jī)發(fā)展初期還沒有高級(jí)語言和編譯器,因此出現(xiàn)了匯編語言。僅僅這樣一個(gè)微創(chuàng)新,就大大提升了開發(fā)效率。

匯編語言常見的語法是 指令 參數(shù)1, 參數(shù)2 ,指令不同參數(shù)也不同。指令即 addmov jmp 等常見的算數(shù)、邏輯運(yùn)算和跳轉(zhuǎn)等功能,參數(shù)可以是立即數(shù)、內(nèi)存地址、寄存器。因此,匯編語言編程能深入到計(jì)算機(jī)編程的最底層,通常說匯編語言是一種“面向機(jī)器的語言 / 編程”。正是因?yàn)檫@個(gè)特點(diǎn),使得匯編語言能提供所有編程語言中最大的時(shí)間和空間的效率,因此至今依然活躍在某些計(jì)算機(jī)領(lǐng)域

匯編語言都是針對(duì)特定的計(jì)算機(jī)體系結(jié)構(gòu)的,例如 x86 匯編(本課重點(diǎn)內(nèi)容)、MIPS 匯編、ARM 匯編,因此沒有讓所有計(jì)算機(jī)都通用的匯編語言。

高級(jí)語言的開發(fā)者,學(xué)習(xí)匯編有何用

一句話總結(jié)就是:了解程序是在計(jì)算機(jī)中是如何被執(zhí)行的,即透過現(xiàn)象(高級(jí)語言)看本質(zhì) —— 這是所有領(lǐng)域的技術(shù)人員都應(yīng)該追求的東西。那些能隨意在 php java js C++ 等語言中隨意切換的程序猿大牛,我想他肯定熟知這個(gè)本質(zhì)。

無論你日常編寫的語言多么高級(jí),肯定最終經(jīng)過轉(zhuǎn)換(編譯原理的內(nèi)容)然后生成匯編語言這種最底層的語言,再被計(jì)算機(jī)執(zhí)行。而“執(zhí)行”的本質(zhì),就可以通過匯編語言的一行一行代碼看出:使用了哪個(gè)指令、獲取了哪個(gè)內(nèi)存地址、操作了哪個(gè)內(nèi)存片段或者寄存器……

另外一個(gè)重要的部分就是程序執(zhí)行的時(shí)候的內(nèi)存模型。一段程序拿過來,哪些變量將被放在棧 stack ,哪些變量將被放在堆 heap ?以及這些內(nèi)存空間如何被釋放?甚至是你日常遇到的爆棧、內(nèi)存泄露等問題,了解了內(nèi)存模型,這些都會(huì)變的非常具象,不再懵。

指令集分類

所謂“指令集”,我理解就是一套操作 CPU 的指令體系集合,以及體系規(guī)范。指令集是一種上層定義,匯編就是其具體的體現(xiàn)和實(shí)現(xiàn)。指令集分兩類:

  • CISC 復(fù)雜指令集,以 x86 為代表(x86 在 PC 服務(wù)器領(lǐng)域具有統(tǒng)治地位)
  • RISC 精簡指令集,以 ARM MIPS 為代表(ARM 統(tǒng)治了手機(jī)和平板領(lǐng)域,MIPS 常用語手機(jī)、電腦之外的其他電子設(shè)備)

CISC

最初的計(jì)算機(jī)編程很麻煩,例如用紙帶打孔輸入,因此計(jì)算機(jī)的設(shè)計(jì)者就考慮將 CPU 做的復(fù)雜一點(diǎn),以簡化這種本來就很麻煩的編程。因此有了 CISC 復(fù)雜指令集。x86 就是其中的典型代表,x86 的特點(diǎn)是:

  • 指令向下兼容(這是其商業(yè)成功的重要因素之一!?。。?,缺點(diǎn)就是會(huì)讓指令集越來越大、越來越復(fù)雜,功耗也更大(因此不適用于低功耗設(shè)備)
  • 變長指令(MIPS 是等長的,只有 32 位),優(yōu)點(diǎn)是節(jié)省空間、擴(kuò)展性好,缺點(diǎn)是譯碼復(fù)雜
  • 多種尋址方式
  • 通用寄存器個(gè)數(shù)有限,x86-32 只有 8 個(gè)通用寄存器,x86-64 也只有 16 個(gè)寄存器
  • 指令中,最多能有一個(gè)操作數(shù)在內(nèi)存中,其他的操作數(shù)必須是立即數(shù)或者寄存器

RISC

歷史原因,RISC 是 80 年代初發(fā)明的,那時(shí)整個(gè)計(jì)算機(jī)生態(tài)系統(tǒng)已經(jīng)形成,編譯器能力增強(qiáng),就不需要 CPU 對(duì)外暴露過度復(fù)雜的指令集,因此有了 RISC 精簡指令集。MIPS ARM 是 RISC 的代表,RISC 指令集特點(diǎn)是:

  • 只關(guān)注一些簡單常用的指令,因此簡單輕量、高性能、功耗低
  • 那些不常用的復(fù)雜指令,就依賴于編譯器(即用軟件來實(shí)現(xiàn),而不是依賴于硬件的復(fù)雜指令),那時(shí)編譯器已經(jīng)比較強(qiáng)大

MIPS 特點(diǎn):

  • 以寄存器為中心。一出手就是 32 位(即寄存器是 32 位的),而且有 32 個(gè)通用寄存器
  • 只有 load 和 store 指令可以訪問內(nèi)存,其他指令只能操作寄存器和立即數(shù)(以寄存器為中心嘛)
  • 指令格式規(guī)范,長度一致(32 位),導(dǎo)致空間利用率不高,但是譯碼效率高
  • 尋址方式非常簡單

ARM 指令集特點(diǎn):

  • 大多數(shù)指令支持“條件執(zhí)行”模式,能使得代碼比較精簡
  • 具有 16 位壓縮指令集,低功耗、低存儲(chǔ)場景下很適用(ARM 在移動(dòng)領(lǐng)域取得很大的成功,如 iphone 上的 A 系列處理器)

兩者對(duì)比。

現(xiàn)代計(jì)算機(jī)中,像 x86 結(jié)構(gòu)雖然也是 CISC ,但那時(shí)對(duì)外的,內(nèi)部實(shí)現(xiàn)還是類似 RISC 實(shí)現(xiàn)的。因此,隨著歷史發(fā)展 CISC 和 RISC 的界限也越來越模糊。如果非要區(qū)分兩者,可以看是不是只允許 load 和 store 操作主存。

數(shù)字的二進(jìn)制表示

數(shù)字用二進(jìn)制表示終歸是一個(gè)數(shù)學(xué)問題,而常用的文本(中文、英文等)如何用二進(jìn)制表示,這就是“編碼”領(lǐng)域的問題。

普通整數(shù)

例如十進(jìn)制 3 的二進(jìn)制表示是 11 這沒問題,但是在計(jì)算機(jī)中表示也是 11 嗎?—— 不對(duì),得分情況。例如在 C 語言中:

  • int 類型占 4 bytes ,即 4 * 8 = 32 bits 。那么 3 在計(jì)算機(jī)中表示就是 00000000 00000000 00000000 00000011 ,即前面要補(bǔ)充上若干個(gè) 0 。
  • short long 等長度不一樣,表示方式也不一樣。因此各類語言中才會(huì)有類型轉(zhuǎn)換。
  • 不同系統(tǒng),或者 32 位、64 位中,表示也不一樣。PS:C 語言指針類型在 32 位系統(tǒng)是 4 bytes ,在 64 位系統(tǒng)是 8 bytes ,因?yàn)樾枰蟮膶ぶ房臻g(支持更大的內(nèi)存空間)。

負(fù)數(shù)

補(bǔ)碼

二進(jìn)制負(fù)數(shù)是通過補(bǔ)碼來表示的,補(bǔ)碼算法是:按位取反、末尾加 1 。為何要用補(bǔ)碼呢?建議讀者看下阮一峰老師的《關(guān)于2的補(bǔ)碼》 http://www.ruanyifeng.com/blog/2009/08/twos_complement.html ,里面講的比我這里詳細(xì)。下面簡單通過一個(gè)例子來說明:

  • 12345 是一個(gè)十進(jìn)制數(shù),其二進(jìn)制是 00110000 00111001(這里暫且假設(shè)一個(gè)整數(shù)占 2 bytes ,這樣簡單)
  • 如果二進(jìn)制想要表示 -12345 這個(gè)負(fù)數(shù),那就用其補(bǔ)碼(即按位取反、末尾加一)得出 11001111 11000111
  • 如果再想將 -12345 變?yōu)檎龜?shù),那么再進(jìn)行補(bǔ)碼運(yùn)算(即按位取反、末尾加一)即可,還會(huì)得到之前的 00110000 00111001 —— 這里體會(huì)到了補(bǔ)碼運(yùn)算的奧秘了,可以來回“搗騰”,完全符合數(shù)學(xué)中對(duì)正數(shù)負(fù)數(shù)的運(yùn)算邏輯
  • 如果要計(jì)算 12345 + (-12345) 的話,只需要將這兩個(gè)二進(jìn)制相加,得到 1 00000000 00000000 ,但是這里一個(gè)整數(shù)只有 2 bytes ,因此第一位的 1會(huì)被移除,得到的正好是 00000000 00000000 ,和數(shù)學(xué)運(yùn)算一樣 —— 又一次感受到補(bǔ)碼運(yùn)算的奧秘?。?!
  • 而且,計(jì)算機(jī)只有加法器沒有減法器,如果計(jì)算 a - b,就會(huì)轉(zhuǎn)換為 a + (-b) ,其中采用補(bǔ)碼計(jì)算 -b ,然后直接做加法運(yùn)算。這樣也從硬件上節(jié)省了資源

有符號(hào)數(shù)和無符號(hào)數(shù)

計(jì)算機(jī)肯定是看不懂正數(shù)、負(fù)數(shù)的,它只能識(shí)別二進(jìn)制數(shù)字。那么計(jì)算機(jī)如何知道一個(gè)數(shù)是正數(shù)還是負(fù)數(shù)呢?要看兩點(diǎn):

  • 這個(gè)變量的類型是有符號(hào)還是無符號(hào),C 語言中有相關(guān)的語法
  • 如果是無符號(hào)數(shù),則正常解析。如果是有符號(hào)數(shù),則判斷第一位:第一位是 0 則是正數(shù),第一位是 1 則是負(fù)數(shù) —— 這僅僅是軟件邏輯上的一個(gè)約定。
  • 因此,有符號(hào)數(shù)的可用存儲(chǔ)空間,比無符號(hào)數(shù)要少一個(gè) bit ,因?yàn)榈谝粋€(gè) bit 要表示符號(hào)

因此 C 語言中的數(shù)字類型就有很多種,適用于不同長度。而每種數(shù)字類型,又分有符號(hào)性和無符號(hào)型。即便是是0也可以有符號(hào)或者無符號(hào)兩種表示,因?yàn)閮烧邔?duì)二進(jìn)制代碼的解析方法不一樣。

PS:日常開發(fā)中,盡量別用無符號(hào)數(shù),會(huì)帶來運(yùn)算問題。C 語言中,有符號(hào)數(shù)和無符號(hào)數(shù)一起進(jìn)行算數(shù)運(yùn)算是,會(huì)將有符號(hào)數(shù)轉(zhuǎn)換為無符號(hào)數(shù)(負(fù)數(shù)第一 bit 的 1就不再代表負(fù)數(shù)了)再進(jìn)行運(yùn)算,很危險(xiǎn)!??!除非特殊場景,例如摸運(yùn)算或者按位運(yùn)算。

其他

除法計(jì)算比較復(fù)雜,如果遇到以 2 為底數(shù)的除法,盡量使用位運(yùn)算。例如 js 中的 >> 。64 >> 2 === 16 ,即將 64 轉(zhuǎn)換為 2 進(jìn)制,然后整體右移 2 位。這種運(yùn)算效率會(huì)非常快 —— 但是估計(jì)現(xiàn)代編譯器會(huì)捕捉到這一特點(diǎn),將除法自動(dòng)編譯為位運(yùn)算。

浮點(diǎn)數(shù)

浮點(diǎn)數(shù)的二進(jìn)制表示比較復(fù)雜,細(xì)節(jié)部分可以忽略

十進(jìn)制小數(shù)如何轉(zhuǎn)換為二進(jìn)制小數(shù)

規(guī)則是:整體規(guī)則是“乘 2 取整,順序排列”,例如:

  • 十進(jìn)制 0.5 二進(jìn)制就是 0.1
    • 0.5 * 2 = 1,取整數(shù) 1
  • 十進(jìn)制 0.25 二進(jìn)制就是 0.01
    • 0.25 * 2 = 0.5 ,取整數(shù) 0
    • 0.5 * 2 = 1 ,取整數(shù) 1
  • 十進(jìn)制的 0.2 二進(jìn)制就是 0.00110011001100110011…… 無限循環(huán)
    • 0.2 * 2 = 0.4 ,取整數(shù) 0
    • 0.4 * 2 = 0.8 ,取整數(shù) 0
    • 0.8 * 2 = 1.6 ,取整數(shù) 1
    • 0.6 * 2 = 1.2 ,取整數(shù) 1
    • 0.2 * 2 = 0.4 ,取整數(shù) 0
    • …… 無限循環(huán)了(只能到某個(gè)精度為止)

因此,二進(jìn)制能精確表示的小數(shù),只能是若干次 *2 能得到整數(shù)的值。其他情況如 0.2 就無法精確表示,只能精確到某個(gè)度,因此 C 語言才有單精度 float 和雙精度 double 浮點(diǎn)數(shù)。

浮點(diǎn)數(shù)的二進(jìn)制存儲(chǔ)

IEEE (美國電器與電子工程師協(xié)會(huì))的浮點(diǎn)數(shù)標(biāo)準(zhǔn)參考一下 http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html,即將一個(gè)存儲(chǔ)空間分成三段:

  • 符號(hào)位 S ,都占 1 bit ,0 表示非負(fù)數(shù),1 表述負(fù)數(shù)
  • 指數(shù) E
  • 有效數(shù)字 M

通過以上幾個(gè)區(qū)域能計(jì)算出它存儲(chǔ)的浮點(diǎn)數(shù)的數(shù)值,按公式 V = (-1)^S * M * 2^E 。不同精度的浮點(diǎn)數(shù),這幾個(gè)區(qū)間的大小不一致:

  • 32 位浮點(diǎn)數(shù) float 中:S 占 1 bit , E 占 8 bit ,M 占 23 bit ,總共 32 bit
  • 64 位浮點(diǎn)數(shù) double 中:S 占 1 bit , E 占 11 bit ,M 占 52 bit ,總共 32 bit

整數(shù)和浮點(diǎn)數(shù)的轉(zhuǎn)換

  • 浮點(diǎn)數(shù)轉(zhuǎn)換為 int ,直接舍棄小數(shù)部分
  • int 轉(zhuǎn)換為 double ,能精確轉(zhuǎn)換。因?yàn)?double 的存儲(chǔ)部分 M 比 int 的 32 位要大
  • int 轉(zhuǎn)換為 float ,不會(huì)溢出但可能會(huì)被舍入 。因?yàn)?float 存儲(chǔ)部分 M 只有 23 位,沒有 int 的 32 位大

x86 計(jì)算機(jī)系統(tǒng)結(jié)構(gòu)

我感覺這部分算是對(duì)計(jì)算機(jī)組成原理的一個(gè)簡單介紹,但我更加推薦大家去專門的計(jì)算機(jī)組成原理的課程去詳細(xì)學(xué)習(xí)。

計(jì)算機(jī)系統(tǒng)結(jié)構(gòu)模型

主要結(jié)構(gòu)分為:

  • CPU ,內(nèi)存,輸入輸出設(shè)備
  • 它們之間通過系統(tǒng)總線連接

x86 的保護(hù)模式

8086 是 intel 在 1978 年發(fā)布的 16 位處理器,80386 是 1985 年 intel 發(fā)布的 32 位處理器(寄存器 32 位)。80386 有三種工作模式:

  • 實(shí)模式:相當(dāng)于一個(gè)可進(jìn)行 32 未運(yùn)算的 8086
  • 保護(hù)模式:(最重要!?。。┩ㄟ^對(duì)程序使用的存儲(chǔ)區(qū)進(jìn)行分段、分頁的存儲(chǔ)管理機(jī)制,達(dá)到分級(jí)使用、互不干擾的目的。通俗來說,即多個(gè)程序同時(shí)運(yùn)行時(shí)互不干擾,為每個(gè)任務(wù)提供一臺(tái)虛擬處理器,使每個(gè)任務(wù)單獨(dú)運(yùn)行、互不干擾。
  • 虛擬 8086 模式:保護(hù)模式下同時(shí)模擬多個(gè) 8086 處理器

有了保護(hù)模式,編程人員才可以在一個(gè)私有的空間內(nèi)為所欲為。

和匯編程序相關(guān)的結(jié)構(gòu)

就好像程序猿占有了一個(gè)(虛擬的) CPU 和一段內(nèi)存地址

  • CPU 中包括 PC 寄存器,表示小一條指令的地址
  • CPU 中包括寄存器和寄存器堆,以名字來訪問的快速存儲(chǔ)單元
  • CPU 中有條件碼,用于存儲(chǔ)最近執(zhí)行指令的結(jié)果狀體信息,用于條件指令的判斷執(zhí)行
  • 內(nèi)存即以字節(jié)編碼的連續(xù)存儲(chǔ)空間,存放代碼、數(shù)據(jù)、運(yùn)行棧、以及操作系統(tǒng)數(shù)據(jù)

這部分內(nèi)容中,寄存器的知識(shí)對(duì)于匯編語言是很重要的,阮一峰老師的博客中也介紹了寄存器,大家可以去參考。

程序執(zhí)行時(shí)的內(nèi)存模型

匯編語言是面向機(jī)器的最基礎(chǔ)編程,既然是編程就涉及到內(nèi)存的使用和分配,于是就有了內(nèi)存模型。這部分的知識(shí),我感覺阮一峰老師的博客中已經(jīng)寫的很詳細(xì)了,我也會(huì)參考他的文章來進(jìn)行下文的總結(jié)。

分配內(nèi)存空間

某個(gè)程序開始運(yùn)行之前,操作系統(tǒng)會(huì)給它分配一段內(nèi)存空間,用于存儲(chǔ)改程序時(shí)使用的、產(chǎn)出的數(shù)據(jù)。具體這塊內(nèi)存區(qū)域的大小和起止指針先不用關(guān)心。

棧 Stack

棧這個(gè)數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)是“先進(jìn)后出”。像 C 語言這種“過程調(diào)用過程”后者“函數(shù)調(diào)用函數(shù)”的執(zhí)行方式,最先調(diào)用的過程或者函數(shù),會(huì)是最后一個(gè)結(jié)束。這一特點(diǎn)和棧的特點(diǎn)基本一致。

需要強(qiáng)調(diào)一點(diǎn),在整個(gè)這段內(nèi)存空間中,棧是自上(高地址)而下(低地址)進(jìn)行累積的,即棧頂?shù)膬?nèi)存地址比棧底的內(nèi)存地址要小。這一點(diǎn)和堆正好相反,如下圖:

壓棧 push

當(dāng)一個(gè)過程或者函數(shù)被執(zhí)行時(shí),會(huì)有一些數(shù)據(jù)(參數(shù)、局部變量、返回地址)需要臨時(shí)存儲(chǔ)起來。而且在“函數(shù)調(diào)用函數(shù)”的整個(gè)過程中,會(huì)有很多這樣的操作。那么就在每個(gè)函數(shù)執(zhí)行時(shí),將這些數(shù)據(jù)壓棧。如下圖,注意調(diào)用鏈和壓棧的關(guān)系(其中兩個(gè) amI 是發(fā)生了遞歸調(diào)用)。

當(dāng)前正在執(zhí)行的函數(shù)對(duì)應(yīng)的棧,叫做“棧幀”,%ebp 和 %esp 兩個(gè)寄存器分別存儲(chǔ)了該棧幀兩端的地址。

PS:遞歸和循環(huán)雖然都可以滿足某些計(jì)算場景,但是在構(gòu)建內(nèi)存模型上是完全不一樣的,遞歸復(fù)雜度更高。

出棧 pop

棧中的數(shù)據(jù)是有聲明周期的,每個(gè)函數(shù)執(zhí)行完 return 之后,其對(duì)應(yīng)的數(shù)據(jù)就要被 pop ,并釋放這段內(nèi)存空間。因此棧的內(nèi)存空間是由系統(tǒng)分配、系統(tǒng)自動(dòng)釋放,不需要人為干預(yù)。人只管好好寫自己的程序就 OK 了。

可以拿上圖中的調(diào)用鏈和棧寫一個(gè)詳細(xì)的調(diào)用過程:

  • 尚未開始調(diào)用,??梢砸曌魇强盏?/li>
  • yoo 函數(shù)被調(diào)用,yoo 的數(shù)據(jù)被壓棧
  • yoo 函數(shù)中又調(diào)用了 who 函數(shù),who 的數(shù)據(jù)被壓棧
  • who 函數(shù)中又調(diào)用了 amI 函數(shù),amI 的數(shù)據(jù)被壓棧
  • amI 函數(shù)中又遞歸調(diào)用了 amI 函數(shù),amI 的數(shù)據(jù)被壓棧
  • 這里注意:壓棧其實(shí)是自上而下的累計(jì)
  • amI 函數(shù) return ,出棧
  • amI 函數(shù) return ,出棧
  • who 函數(shù) return ,出棧
  • yoo 函數(shù) return ,出棧
  • 最終,棧又回到之前的空狀態(tài)
  • 無論是壓棧還是出棧,%ebp 和 %esp 寄存器一直隨著棧幀的變化而變化

其他

有一個(gè)程序猿知名網(wǎng)站叫 stackoverflow ,意思就是“棧溢出”。按照上述模型的理解,就是程序執(zhí)行時(shí)棧內(nèi)存累計(jì)過多,導(dǎo)致溢出了整個(gè)分配的內(nèi)存空間了。常見的導(dǎo)致這種問題的方式是大量的遞歸調(diào)用,可以用“尾遞歸”來解決這一問題,感興趣的可以去具體查一查。

堆 heap

在整個(gè)程序被分配的內(nèi)存空間里,棧是系統(tǒng)自己使用和分配,自上而下的累積。其中還有一部分內(nèi)存空間是給程序猿使用的,即你可以通過程序動(dòng)態(tài)占有一部分內(nèi)存(如 C 語言的 malloc ,C++ 的 new ,其他高級(jí)語言的引用類型),這部分內(nèi)存叫“堆”。它和棧不一樣:

  • 堆是 自下(內(nèi)存低地址)而上(內(nèi)存高地址) 的累積的
  • 堆沒有“先進(jìn)后出”這種規(guī)則,它就是簡單粗暴的占有和釋放
  • 堆中被占用的內(nèi)存不會(huì)自動(dòng)釋放,需要手動(dòng)釋放,或者通過虛擬機(jī)定期 GC(如常見的引用計(jì)數(shù)方法、標(biāo)記清除方法等)

常說的內(nèi)存泄露就是在堆中占有的內(nèi)存沒有被及時(shí)的清理或者 GC ,導(dǎo)致長時(shí)間積累之后內(nèi)存崩潰。對(duì)于 JS 開發(fā)者,應(yīng)該知道 Chrome devtools 中有一個(gè) heap Snapshot ,用來記錄當(dāng)前時(shí)刻 JS 堆內(nèi)存,如下圖:

常見數(shù)據(jù)結(jié)構(gòu)的存儲(chǔ)方式

以 C 語言中的數(shù)組和結(jié)構(gòu)體為例。

C 語言中,數(shù)組需要一個(gè)連續(xù)的存儲(chǔ)空間,每個(gè)數(shù)組需要一個(gè) L * sizeOf(T) 字節(jié)的空間。例如 10 個(gè) int 元素的數(shù)組,其空間就需要 10 * 4 = 40 bytes 的空間。通過這個(gè)存儲(chǔ)格式,就可以很容易的遍歷、訪問到數(shù)據(jù)的每個(gè)元素。用 %edx 寄存器存儲(chǔ)起始地址,用 %eax 表示 index ,那么 (%edx, %eax, 4) 就是這個(gè)當(dāng)前元素的內(nèi)存地址(4 即取出 4 bytes 長度的內(nèi)容,int 類型占 4 bytes)。二維數(shù)組也是同樣的道理。

PS:數(shù)組和鏈表有時(shí)候看著用途一樣,但是數(shù)據(jù)結(jié)構(gòu)上是有明顯區(qū)別的,鏈表不需要一個(gè)連續(xù)的存儲(chǔ)空間。

C 語言結(jié)構(gòu)體也需要一個(gè)連續(xù)的存儲(chǔ)空間,結(jié)構(gòu)提內(nèi)部通過名字訪問,每個(gè)元素都可以有不同的類型。

  1. struct rec {
  2. int i;
  3. init a[3];
  4. int *p; // *p 表示一個(gè)內(nèi)存地址,&p 可以獲取該地址的值
  5. }

以上代碼將會(huì)被分配這樣一個(gè)連續(xù)的內(nèi)存地址:0 - 4 存放 i(4 bytes),接著 5 - 16 存放數(shù)組(3 個(gè) int),接著 16 - 20 存放指針(32 位指針)。

簡單的匯編指令

雖然本課是主講匯編語言,課程中也花了大量的時(shí)間講解了常用的指令、示例以及 C 語言和匯編語言的如何對(duì)應(yīng)。不過對(duì)于我這種以了解匯編、學(xué)習(xí)基礎(chǔ)知識(shí)為目的的高級(jí)語言的開發(fā)者,并沒有去認(rèn)真聽每個(gè)指令的具體意義。不知道這是不是常說的“不求甚解”。

簡單指令

課程中幾個(gè)比較簡單的匯編指令如下:(阮一峰老師的博客中也講了一些常用指令,講的更加詳細(xì),可以去學(xué)習(xí))

  • addl 參數(shù)1, 參數(shù)2 加法
  • movl Source, Dest 賦值
  • leal Source, Dest 計(jì)算出地址賦值給 Dest
  • cmpl Src2, Src1 比較,類似于計(jì)算 Src2 - Src1

上述兩個(gè)指令,add 和 mov 等表示指令類型,后面的 l 是一個(gè)后綴,表示一次性操作 2 bytes 。這樣的后綴還有很多,例如 b w ,都有不同的含義,不過不用去管它。

參數(shù)中,%edx 表示某個(gè)寄存器,(%edx) 表示將這個(gè)寄存器的值作為內(nèi)存地址,$ 開頭的是一個(gè)立即數(shù)。8(%edx) 找到某個(gè)內(nèi)存地址并連續(xù)讀取 8 bits 內(nèi)容(如 int 類型就占 8 bits)。

條件碼和分支

上文中【和匯編程序相關(guān)的結(jié)構(gòu)】圖中可以看到,CPU 中有“條件碼”。例如,x86 中常用的四個(gè)條件碼(其實(shí)我也不知道怎么用……)

  • CF,Carry(進(jìn)位) Flag
  • SF,Sing Flag
  • ZF,Zero Flag
  • OF,OverFlow Flag

(每個(gè)條件碼只占 1 bit 空間,可見它是一個(gè) boolean 型的存在)

在指令運(yùn)行過程中,硬件會(huì)根據(jù)指令運(yùn)行的狀態(tài)實(shí)時(shí)的修改這些條件碼的值,然后用 set 指令,從條件碼中讀出來,放入通用寄存器中,然后就可以用于分支跳轉(zhuǎn)了。細(xì)節(jié)沒具體看。

跳轉(zhuǎn)

以 j 開頭的一系列指令,滿足不同的條件即可跳轉(zhuǎn)到某個(gè)程序塊。例如 jmp 是無條件跳轉(zhuǎn),je 是 ZF 條件碼為 0 時(shí)才跳轉(zhuǎn),jne 是 ZF 條件碼不是 0 時(shí)才跳轉(zhuǎn)。跳轉(zhuǎn)的語法類似于 C 語言的 goto 語句,但在 C 語言中不推薦使用 goto 語句。

執(zhí)行邏輯驗(yàn)證

高級(jí)編程語言中有三種基本的執(zhí)行邏輯:第一,順序執(zhí)行,這個(gè)對(duì)應(yīng)匯編語言沒啥問題;第二,分支執(zhí)行(即 if else);第三,循環(huán)執(zhí)行。后兩種,通過判斷條件碼和跳轉(zhuǎn)也都可以實(shí)現(xiàn)。

關(guān)于遞歸,課程中也講了很多內(nèi)容,不過我沒看懂(沒有那么那么認(rèn)真的看,看不懂就算了……)。

程序示例

如果想簡單看一下匯編語言是什么樣子的,可以通過 gcc 編譯一段簡單的 C 語言來看下。首先,新建一個(gè) hello.c 的文件然后寫上如下內(nèi)容并保存。

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. int main() {
  4. printf("hello word
  5. ");
  6. exit(0);
  7. return 0;
  8. }

在該文件目錄中運(yùn)行 gcc -S -O2 -m32 hello.c ,然后即可看到生成了一個(gè) hello.s 的文件,內(nèi)容如下:

  1. .section __TEXT,__text,regular,pure_instructions
  2. .macosx_version_min 10, 12
  3. .globl _main
  4. .p2align 4, 0x90
  5. _main: ## @main
  6. ## BB#0:
  7. pushl %ebp
  8. movl %esp, %ebp
  9. subl $8, %esp
  10. calll L0$pb
  11. L0$pb:
  12. popl %eax
  13. leal L_str-L0$pb(%eax), %eax
  14. movl %eax, (%esp)
  15. calll _puts
  16. movl $0, (%esp)
  17. calll _exit
  18. subl $4, %esp
  19. .section __TEXT,__cstring,cstring_literals
  20. L_str: ## @str
  21. .asciz "hello word"
  22. .subsections_via_symbols

這就是 C 語言編譯出來的匯編語言。具體的示例,可以去看阮一峰老師那篇博客最后的內(nèi)容,他在博客中對(duì)一段匯編語言做了詳細(xì)的解釋。我這里就省略了。

最后

僅僅是一個(gè)學(xué)習(xí)筆記,發(fā)現(xiàn)錯(cuò)誤歡迎指正。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
匯編語言入門教程
《深入理解計(jì)算機(jī)系統(tǒng)》閱讀總結(jié)與摘要
sse2指令集
我也要學(xué)匯編語言-第一課:機(jī)器語言編程 - dodolook - 博客園
匯編語言學(xué)習(xí)筆記之存儲(chǔ)器的管理模式
操作系統(tǒng)原理詳解匯編語言基礎(chǔ)知識(shí)(圖文代碼) (堆棧的初始化時(shí)通過設(shè)置SS及SP值來完成的可以由編譯系統(tǒng)自動(dòng)完成也可以在程序中通過偽指令顯示地定義)
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服