首頁>Club>
組合語言如何發展到C語言的,C語句和指令如何一一對應的?
13
回覆列表
  • 1 # muzilovemusic

    程式語言的發展大致可以分為四個階段:機器語言,組合語言,面向過程語言,面嚮物件語言。

    機器語言

    機器語言就是一串01程式碼,這串01程式碼可以經硬體識別並執行,所有語言最終都會轉換為機器語言。

    組合語言

    組合語言就是將一串很枯燥無味的機器語言轉化成一個英文單詞。比如說:add 1, 2;

    add 就是一個英文單詞,這樣看起來就稍微有一些含義了,即 1 和 2 相加。

    如果直接用機器語言編寫的話,這幾乎是無法實現的。因為用機器語言太難記憶了,也沒人能看得懂。所以後來就設計出了第二種語言,即將 0/1 程式碼翻譯為英文單詞,這些英文單詞直接對應著一串 0/1 指令。這個就是組合語言。

    從組合語言到c語言

    彙編到c語言的最多變化在於,以函式為單位的呼叫關係。那麼這個呼叫關係在彙編下是怎麼體現的呢?

    具體來說:c語言的函式關係在彙編下變成了什麼?

    棧就是這個變化的關鍵,對的變化就體現了函式的呼叫關係。每一個新函式的呼叫意味這一個新棧的建立。一個函式呼叫的結束有就伴隨這棧的結束。

  • 2 # 壹貳壹壹

    二、反彙編技巧(摘取自 天書夜讀)

         2.1  首先,將流程程式碼與資料計算的程式碼分開,我們使用

                F: 用於標識 呼叫函式或者作為函式被呼叫,出棧、入棧

                C:用於標識 流程控制程式碼、判斷、比較、迴圈

                D: 用於標識 資料處理

         2.2  翻譯程式碼

               一般被讀入的為輸入,被寫的內部變數為輸出;

               取出D的程式碼進行逐句翻譯,任何一段不加任何跳轉、連續的mov 和 加減乘除都可以還原成為一個表示式;

               標識為F的程式碼基本不用翻譯,本身為簡單的函式呼叫。

               C的程式碼將其翻譯成為if 、for、do、switch。

         2.3  表示式合併 與  控制流程結合,整理出來;

  • 3 # 武漢朝夕教育科技

    我給你說說二者的區別,再去看看演變歷史!

    一、組合語言是什麼?

    我們知道,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 是由於函式執行而臨時佔用的記憶體區域。請看下面的例子。

    1234

    上面程式碼中,系統開始執行main函式時,會為它在記憶體裡面建立一個幀(frame),所有main的內部變數(比如a和b)都儲存在這個幀裡面。main函式執行結束後,該幀就會被回收,釋放所有的內部變數,不再佔用空間。如果函式內部呼叫了其他函式,會發生什麼情況?

    12345

    上面程式碼中,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 指令

    7.1 一個例項

    瞭解暫存器和記憶體模型以後,就可以來看組合語言到底是什麼了。下面是一個簡單的程式example.c。

    123456

    gcc 將這個程式轉成組合語言。

    1

    上面的命令執行以後,會生成一個文字檔案example.s,裡面就是組合語言,包含了幾十行指令。這麼說吧,一個高階語言的簡單操作,底層可能由幾個,甚至幾十個 CPU 指令構成。CPU 依次執行這些指令,完成這一步操作。example.s經過簡化以後,大概是下面的樣子。

    12345678910111213

    可以看到,原程式的兩個函式add_a_and_b和main,對應兩個標籤_add_a_and_b和_main。每個標籤裡面是該函式所轉成的CPU 執行流程。 每一行就是 CPU 執行的一次操作。它又分成兩部分,就以其中一行為例。

    1

    這一行裡面,push是 CPU 指令,%ebx是該指令要用到的運運算元。一個 CPU 指令可以有零個到多個運運算元。下面我就一行一行講解這個彙編程式,建議讀者最好把這個程式,在另一個視窗複製一份,省得閱讀的時候再把頁面滾動上來。

    7.2 push 指令

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

    1

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

    1

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

    7.3 call 指令

    第三行的call指令用來呼叫函式。

    1

    上面的程式碼表示呼叫add_a_and_b函式。這時,程式就會去找_add_a_and_b標籤,併為該函式建立一個新的幀。下面就開始執行_add_a_and_b的程式碼。

    1

    這一行表示將 EBX暫存器裡面的值,寫入_add_a_and_b這個幀。這是因為後面要用到這個暫存器,就先把裡面的值取出來,用完後再寫回去。這時,push指令會再將 ESP 暫存器裡面的地址減去4個位元組(累計減去12)。

    7.4 mov 指令

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

    1

    這一行程式碼表示,先將 ESP 暫存器裡面的地址加上8個位元組,得到一個新的地址,然後按照這個地址在 Stack 取出資料。根據前面的步驟,可以推算出這裡取出的是2,再將2寫入 EAX 暫存器。下一行程式碼也是幹同樣的事情。

    1

    上面的程式碼將 ESP 暫存器的值加12個位元組,再按照這個地址在 Stack 取出資料,這次取出的是3,將其寫入 EBX 暫存器。

    7.5 add 指令

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

    1

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

    7.6 pop 指令

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

    1

    上面的程式碼表示,取出 Stack 最近寫入的值(即 EBX 暫存器的原始值),再將這個值寫回 EBX 暫存器(因為加法已經做完了,EBX 暫存器用不到了)。注意,pop指令還會將 ESP 暫存器裡面的地址加4,即回收4個位元組。

  • 中秋節和大豐收的關聯?
  • 如果外星人進攻地球,你認為哪個國家的軍隊適合主導這場戰爭?