深入理解計算機系統(十四):程序編碼以及數據格式
5 人贊了文章
目錄
1、機器級代碼
2、程序存儲器
3、程序示例
4、數據格式
在進行本章的講解之前,我們先說明講解的機器語言型號。上一篇深入理解計算機系統(十三):彙編語言和機器語言我們講過,機器語言是直接面向處理器(Processor:CPU)的程序設計語言,但是每一種這樣的微處理器(CPU)由於硬體設計和內部結構的不同,所以每一種微處理器都有自己的機器指令集,也就是機器語言。而彙編語言是便於記憶的機器語言。本系列博客將會介紹兩種相關的機器語言:Intel IA32 和 x86-64。前者是當今大多數計算機的主導語言,而後者是在 64 位機器上運行的擴展,我們先從 Intel IA32開始。
正文:
1、機器級代碼
前面我們就說過,計算機系統使用了多種不同的抽象,利用更簡單的抽象模型來隱藏實現的細節。對於機器級編程來說,有兩種抽象特別重要:
①、第一種是將機器級程序的格式和行為定義為指令集體系結構(Instruction set architecture ,ISA),它定義了處理器狀態、指令的格式,以及每條指令對狀態的影響。大多數 ISA,包括 Intel IA32 和 x86-64,將程序的行為描述成好像每條指令是按順序執行的,即一條指令結束後,下一條指令開始。處理器的硬體遠比描述的精細複雜,它們並發的執行許多指令,但是可以採取措施保證整體行為與 ISA 指定的順序執行完全一致。
②、第二種是機器程序使用的存儲器地址是虛擬地址,提供的存儲器模型看上去是一個非常大的位元組數組。存儲器系統的實際實現是將多個硬體存儲器和操作系統軟體組合起來。
在整個編譯過程中,編譯器會完成大部分工作,將把用 C 語言提供的相對比較抽象的執行模型表示的程序轉化成處理器執行的基本指令,也就是彙編語言,彙編語言在被彙編器轉化成機器語言,然後計算機去執行。彙編語言也就是具有更好的可讀性的機器語言,所以能夠理解彙編代碼以及它與原始 C 代碼的關係,是理解計算機如何執行程序的關鍵步驟。
我們在寫 C 程序時,處理器的狀態都是隱藏的,即我們編碼不用去直接操作處理器。但是在彙編語言中,如下的幾個處理器狀態是可見的:
一、程序計數器(在 IA32 中通常稱為 PC,用 %eip 表示):指示將要執行的下一條指令在存儲器中的地址。
二、整數寄存器文件:包含8個命名的位置,可以存儲一些地址或者整數的數據。有的用來記錄某些重要的程序狀態,有的則用來保存臨時數據。
三、條件碼寄存器:保存最近執行的算數或邏輯指令的狀態信息,它們用來實現控制或數據流中的條件變化,比如用來實現 if 和 while 語句。
四、浮點寄存器:存儲浮點數。
注意:C 語言提供的模型可以在存儲器中聲明和分配各種數據類型的對象。但是實際上機器代碼則只是簡單的將存儲器看成是一個很大的、按位元組定址的數組。
彙編代碼不區分有符號或者無符號整數,不區分各種類型的指針。甚至不區分指針和整數。
2、程序存儲器
程序存儲器包含程序的可執行機器代碼,操作系統需要的一些信息,用來管理過程調用和返回的運行時棧,以及用戶分配的存儲器塊。
程序存儲器用虛擬地址來定址,在任意給定的時刻,只認為有限的一部分虛擬地址是合法的。操作系統則負責管理虛擬地址空間,將虛擬地址翻譯成實際處理器存儲器(processor memory)中的物理地址。
3、程序示例
如下這是一段 C 程序代碼 hello.c:
#include <stdio.h> int main(){ return sum(1,3);}int accum = 0;int sum(int x,int y){ int t= x+y; accum += t; return t; }
然後執行如下命令生成彙編程序
gcc -O1 -S hello.c
-O1是優化選項,少優化->多優化:
O0 -->> O1 -->> O2 -->> O3
-O0表示沒有優化,-O1為預設值,-O3優化級別最高
生成的彙編程序 hello.s
.file "hello.c" .text.globl sum .type sum, @function //定義全局函數sumsum:.LFB12: .cfi_startproc leal (%rsi,%rdi), %eax //把寄存器%rsi和寄存器%rdi的值的地址裝入eax中,即&(rsi+rdi)=eax addl %eax, accum(%rip) //把寄存器%eax和寄存器%rip的值相加,並存放到 %rip中 ret .cfi_endproc.LFE12: .size sum, .-sum.globl main //主函數main .type main, @functionmain:.LFB11: .cfi_startproc movl $3, %esi //將數據3複製到%esi寄存器 movl $1, %edi movl $0, %eax call sum //將 sum 指令的地址壓入到棧中,也就是下一條指令執行調用 sum 函數 rep ret .cfi_endproc.LFE11: .size main, .-main.globl accum //定義全局變數accum .bss .align 4 .type accum, @object .size accum, 4accum: .zero 4 .ident "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-18)" .section .note.GNU-stack,"",@progbits
注意:所有以 『.』 開頭的行都是指導彙編器和鏈接器的命令,我們通常可以忽略這些行。
現在這些彙編指令大家可以不用完全理解,後面會詳細進行講解。
4、數據格式
由於計算機是由16位體系結構擴展為32位體系結構的,Intel 用術語 「字」(word) 表示16位數據類型,因此 32 位表示 「雙字」(double words),64 位數稱為「四字」(quad words).
前面的彙編代碼我們可以看到所有的彙編指令都帶有字母 l,比如movl、addl、subl、pushl等等,這個l的後綴其實就是表示的數據格式,表示我們操作的是32位的數值。
下面我們看一下 C 語言基本數據類型對應的 IA32 表示:
上面的圖示很好理解,比如mov指令,它是一個數據傳送的指令,那麼movb就代表傳送一個位元組的數據,movw就代表傳送兩個位元組的數據,而movl就代表傳送四個位元組的數據。需要注意的是,long long int在IA32架構中是不支持這種數據格式的。而且彙編代碼使用後綴 「l」 來表示 4 位元組整數和8位元組雙精度浮點數,這不會產生歧義,因為浮點數使用的是一組完全不同的指令和寄存器。
推薦閱讀:
※sockets編程 發送http請求
※添物不花錢學計算機及編程(預備篇)
※Pycharm 使用教程看這篇就足夠
※提高工件加工精度與效率的幾個重要方面,這些你都會了嗎?
※C++基礎練習:分治法排序代碼