C語言雜記 -- 簡陋的<深刻理解計算機系統>筆記

 

程序的表示

3264位操做系統是由CPU寄存器的位數決定,即虛擬尋址的範圍爲2^322^64html

字節的大端小端法是以字節爲基本單位的:好比十進制的7在十六位機器上表示node

·          數組

地址緩存

100less

101函數

大端法性能

00000000測試

00000111優化

小端法spa

00000111

00000000

 

大部分編譯器默認進行算數右移和用補碼錶示覆數

對於移位k來講,實際移位量爲k mod 2^ww爲所移動數據類型的位數)

對於整型常量來講c編譯器都是先將其當作正數,若是前面有 - 運算符則對其去負即轉換成補碼錶示。

//這形成了C中及其隱晦的存在

//limits.h

#define INT_MAX 2147483647   // 二進制表示方法爲0111 1111 1111 1111,爲C最大表示的整型數2^31 - 1

#define INT_MIN -INT_MAX - 1)   

/*

爲何不直接寫成-2147483648呢? 這是由於編譯器在看到這個常量時,不會先看取負元算符,而是看到了21474836482^31,編譯器會感受到沒法用int型來表示,便會將其用長整形來表示爲unsigned int 1000 0000 0000 0000,這樣它的類型就爲unsigned,在你進行使用的時候便會發生錯誤,考慮以下代碼

*/

#include <limits.h>

#include <stdio.h>

#include <stdlib.h>

 

int main()

{

    int a = 5;

 

    printf("%d", a > -2147483648); // result is 0, 這是由於比較過程當中發生了隱式轉換,轉換成了unsigned int 之間的比較。

    printf("%d", a > INT_MIN);     // result is 1,二者都是int類型,因此比較成功


    return 0;

}

這就是C語言中,負數最小值爲-2147483648,卻不能直接寫出的緣由。具體請參考http://www.cnblogs.com/Jack47/archive/2013/01/06/TMin32-in-c.html

浮點數:

32位浮點數由一位符號位s8位階碼exp即權值,23位尾數

數值類型

s

exp

尾數f

規格化

s

!=0 && != 255

f(尾數定義爲1+f), 階碼爲exp - 01111111

非規格化

s

0000 0000

尾數爲f(尾數定義爲f, exp1 - 01111111)緣由見下

無窮大

s1,0分別表示正負無窮大)

1111 1111

000000000000000.......

NaN(NOT a NUMBER)

s

1111 1111

!=0

 

緣由保證最小規格化數到最大規格化數的平穩過渡,這樣最小規格化數的exp0000 0001 - 0111 1111 = -126;最大非規格化數的exp1 - 0111 1111 = -126. 這樣就保證了二進制小數數值均勻的接近於零

64位浮點數一位符號位s, 11位階碼, 52位尾數, 原理相同.

浮點數的舍入(多采用向偶數舍入, 又稱其爲向最接近的值舍入): 當值不位於要舍入的中間值時, 向最近的整數舍入; 當值爲中間值時, 向最接近的偶數舍入. 好比1.5舍入爲整數, 由於其位於一和二中間, 向偶數舍入, 2.

彙編指令(ATT格式)

知識準備:32位系統寄存器

名稱

特殊做用

  

%eax

一般做爲函數返回值,

低十六位組成%ax, %ax中高八位爲%ah,低八位爲%al

%ecx

 

同上

%edx

 

同上

%ebx

 

同上

%esi

 

只有低十六位的

%edi

 

同上

%esp

棧指針

同上

%ebp

幀指針

同上

 

操做數(R表示取寄存器的值, M(addr)表示對地址addr的引用

類型

格式

操做數值

例子

當即數

$imm

imm

push $imm: 入棧imm

寄存器

%e

R[e]

push %eax: 寄存器%eax的值入棧

存儲器

(imm)

M[imm]

push (4): 地址4處的值入棧

存儲器

(%e)

M[R[e]]

push (%eax): %eax的值爲地址的值入棧

存儲器

imm(%e)

M[R[e] + imm]

...

存儲器

(Ea, Eb)

M[R[Ea]+ R[Eb]]

...

存儲器

imm(Ea, Eb)

M[R[Ea]+ R[Eb] + imm]

...

存儲器

(, Ei, s)

M[R[Ei] * s]

...

存儲器

(Eb, Ei, s)

M[R[Eb]+ R[Ei] * s]

...

存儲器

imm(Eb, Ei, s)

M[R[Eb]+ R[Ei] * s + imm]

最經常使用

 

數據傳送指令:MOV(S, Z), SD不能同時爲存儲器目標

Mov[b, w, l]:分別傳送[字節, , 雙字],

Movb S, D: S傳送到D

Movs[bw, bl, wl]: 符號擴展

Movz[bw, bl, wl]: 零擴展

Pushl: 雙字入棧, eg, push %eax

Popl: 雙字出棧, eg, pop %eax

算術和邏輯操做

Leal S, D: &SàD

INC D: D++

DEC D: D--

DEG D: -D

NOT D: ~D (取補)

ADD S, D: D = D + S

SUB S, D: D = D - S

IMUL S, D: D = S * D

XOR S, D: D = D ^ S (異或)

OR S, D: D = D | S

AND S, D: D = D & S

SAL(SHL) k, D: D = D << k

SAR k, D: D = D >> k (算數右移)

SHR k, D: D = D >> k (邏輯右移)

特殊的算數操做

imull S: 某個寄存器 = S * R[%eax] (有符號)

mull S: (無符號)

cltd S: R[%eax] 進行符號擴展到某個寄存器

idivl S: 有符號除法, R[%eax] / S; 商放到%eax, 餘數放到%edx

divl S: 無符號除法

控制:條件, 循環

條件(根據條件碼來設置)

cmp[b, w, l]: 分別比較字節, , 雙字.   cmp S1 S2(基於 S2 - S1), 會設置條件碼

set[e(equal), n(not), s(負數),  g(great), l(less), a(above), b(below)] D: 根據條件碼集合將D設置爲10.

test S1, S2(基於S1 & S2, 只改變條件碼, 一般用來測試S1 ?= S2)

j[…set相同, 額外有mp]: jmp LABLE(直接跳轉到LABLE), jmp *op(間接跳轉到對op解引用後, 以解引用後的值進行跳轉)

條件,循環控制多借助於上述組合來使用:eg

test %edx, %eax

je .L3   若是相等則跳轉到.L3

.L3

    僞代碼

cmp %edx, %eax

jne .L3   若是不相等則跳轉到.L3

.L3

    僞代碼

條件傳送指令: 有利於現代處理器更好的執行, 避免分支懲罰

相似於 x < y ? y - x : x - y, 語句能夠產生條件傳送指令

配合上文的條件碼, cmov[set相同], cmovl S, R 若小於則傳送

switch語句配合跳轉表來使用, 只進行一次條件判斷

call Label(* Operand): 過程調用, 將返回地址入棧, 並跳轉到被調用過程的起始地址

ret: call指令做用相反

優化程序性能

消除循環的低效率

對於循環中的過程調用盡可能移出循環外, 例如:

for (i = 0; i < strlen(s); i++) //strlen()函數爲線性增加,在字符串長度很大時,很消耗系統資源

減小沒必要要的存儲器引用, 將存儲器引用儲存在臨時變量中.

處理器優化: 即充分利用存儲器流水線操做的吞吐量

循環展開, 減小讀寫相關, 即所使用的數據必須等待上一次操做完成.

從新結合變換, 減小讀寫相關, eg

for ()

    acc = (acc OP data[i]) OP data[i + 1]

    // acc = acc OP (data[i] OP data[ i + 1]    相比上一行, 處理器能夠更新acc的同時進行(data[i] OP data[i + 1]), 而上一行就必須等待acc更新完以後才能進行(acc OP data[i])操做.

利用局部性原理: 高速緩存

時間局部性: 被引用過一次的存儲器位置可能在不遠的時間內繼續被引用

空間局部性: 若是一個存儲器位置被引用了, 極可能在不遠的未來被引用其附近的存儲器位置

每一級緩存只是關心其上下級緩存的分組狀況, 緩存管理多是硬件自己, 或者軟件, 亦或是兩者的結合

 

內核爲每一個進程維護一個描述符表, 描述符表(每一個進程獨有)是一個結構指針數組, 每一個指針指向打開文件表, 打開文件表(包含當前文件位置, 引用計數等, 全部進程共享)包含一個指向v_node表的指針, v_node表也是包含一些文件基本信息(全部進程共享). 值得注意的是, 內核默認爲每一個進程打開標準輸出, 標準輸入, 標準錯誤輸出三個文件, 若是任何一個打開文件表的引用計數變爲零, 內核則會收回打開文件表中此文件的內存, 在對其引用可能會發生意想不到的錯誤. 子進程與父進程不一樣且繼承的是描述符表, 同時系統藉助描述符表和虛擬存儲器來實現文件共享.

相關文章
相關標籤/搜索