讀後感
這本書是美國“卡內基-梅隆大學(CMU)”的教科書,邏輯嚴謹。雖然是教科書,還是有些晦澀難懂啊,不太形象。第二章主要講整數,浮點數,很是晦澀,全是數學公式。作者的思維數學的思維,動不動就是n、m、k、∑等等,讓我們數學很爛的同學如何是好。如果能以普通人的思維把數學知識加進去就好了。
該書確實系統的介紹了電腦,很完善。它能給你以下幾個重要層級的模型和過程:
1.函數的調用棧模型——第三章(函數不一定都會建立棧幀)
2.a.out或者exe可執行檔的結構——第七章點擊開啟連結
3.程式載入器和連結——第八章 點擊開啟連結
4.malloc和虛擬儲存空間原理——第九章點擊開啟連結
5.線程,在儲存空間中模型——第12章
對於處於成長期的程式員來說,真是欣喜若狂!
有了這些還需要《C專家編程》嗎?哈哈!
翻譯者很是用心,但是讀者不一定領情。比如:可以直接翻譯流行的記憶體、硬碟和固態硬碟,完全沒有必要用主存、磁碟和固態儲存磁碟。還比如:沒有必要把shell翻譯成“外殼”多彆扭啊。這些翻譯者應該像“侯捷”學習。
這本說內容大而散,感覺沒有盡頭一樣。老外怎麼學這種課程,費腦子啊。從電腦結構、二進位表示、到組合語言函數的調用、然後cpu的結構、再有連接器儲存空間、還有進程,並發、更有網路編程,基本大學四年也就學了這麼多東西。
這本書中有句話很有意思:儲存空間的一個有趣的屬性是不論系統中有多大的儲存空間,他總是一種稀缺資源。磁碟空間和垃圾桶同樣有這個屬性。
工作2年多的時間裡,每每都是在網上搜系統方面的知識、編譯、連結和虛擬儲存空間malloc等等。只有讀了這本書才能系統得學到電腦知識。
一、電腦漫遊
---》利用直接儲存空間(DMA)的技術,資料可以不通過cpu而直接從磁碟到達記憶體。
---》根據機械原理,較大的儲存空間比較小的儲存空間運行慢,一個寄存器只能儲存幾百個Byte,而且記憶體可以存放GB以上。加快處理器的運行速度比加快記憶體運行速度更容易。
---》快取至關重要,一個簡單的helloworld揭示了一個重要的問題。系統花費大量時間把資訊從一個地方挪到另一個地方。helloworld最初放在硬碟上,然後載入到記憶體,進而進入cpu中。說明了一個儲存空間階層:
二、資訊的表示和處理
講的是電腦原理,二進位,補碼和浮點數等。因為大學課程已經學習過了,沒有細讀。
---》浮點數,規格化、非規格化和無窮大。
一般來說我們沒把發用小數表示1/3、7/10等這些不能整出的數字,那麼如果用二進位表示十進位的小數,更多的表示不出來。二進位甚至不能表示十進位的0.1和0.2
三、程式的機器級表示(其實就是組合語言)
---》講的是《組合語言》,頭都大了!個人覺得組合語言不用花時間瞭解,即使是本書中的組合語言也有文字解析。IA32和X86-64兩種組合語言。
---》彙編代碼不區分有符號和無符號甚至指標類型。
---》展示了,彙編代碼尾碼的含義:
大多數GCC產生的彙編指令都有一個字元尾碼,表示運算元的大小。例如資料傳送指令有三個變種:movb(傳送位元組)、movw(傳送字)和movl(傳送雙字)。注意,彙編代碼使用尾碼'l'來表示4個位元組整數和8個位元組雙精確度浮點數,這不會產生歧義,因為浮點數使用的是一組完全不同的指令和寄存器。
運算元指示符,運算元一共有三類:1)立即數(immediate)也就是常數值,立即數的書寫方式是$。例如:$0x1F。2)寄存器,3)儲存空間(memory).由於三種運算元的存在所以定址方式就有很多種。
---》一個32位cpu中寄存器的結構如下:
是IA32的整數寄存器。所有8個寄存器都可以作為32位和16位使用,例如%eax和%ax。並且前四個寄存器可以訪問其兩個低位元組。如:%ah和%al。
是64位cpu的寄存器結構圖:
紅色框內,是相容32為cpu的結果。
---》寄存器使用慣例:%eax、%edx和%ecx是調用者儲存寄存器,%ebx、%esi和%edi是被調用者儲存寄存器。那麼,一個函數f()可能被別人調用,也可以調用其他函數,所以當f()運行時需要將%ebx、%esi和%edi儲存到棧中,並在返回前再恢複它們。p(151)---》64位%rax寄存器用來儲存函數的傳回值,p(198)
在x86-64組合語言,中%rax用來儲存函數的傳回值,而在結果返回之前,%rax可以重複利用。
---》棧在處理函數調用中起到至關重要的作用。棧的,棧頂朝下,由於IA32 的棧竟然是往低地址延伸生長,直讓我崩潰。(p115)
圖片的上半部分,說明了實際效果,即將%eax的值移動到%edx中,圖片的下半部分是棧移動步驟。棧頂的變化最後關鍵。從0x108 -> 0x104 -> 0x108
---》棧幀結構,IA32程式用程式棧來支援函數調用。機器用棧來傳遞函數參數、傳回值、儲存寄存器用於以後恢複和本機存放區。為單個過程分配的那部分棧成為棧幀(stack frame)。說了棧幀的結構。
---》call指令。call指令的效果是將傳回值地址入棧,並跳轉到被調用過程的起始處。返回地址是在程式中緊跟在call後面的那條指令的地址。這樣當被調用函數返回時,執行會從此處繼續。ret指令從棧中彈出地址,並跳轉到這個位置。例如下面的代碼:
int accum = 0;int sum(int x,int y);int main(){ return sum(1.3);}int sum(int x,int y){ int t = x + y; accum += t; return t;}
經過反組譯碼後,節選處call部分的代碼如所示:
第一行call指令的效果就是將0x80483e1壓入棧中,同時將%eip(程式計數器)的值設定為sum的第一條指令0x8048394.最後一行的ret指令彈出0x80483e1給%eip,並跳轉到這個地址。:
ret指令的效果就是讓0x080483e1彈出,調整棧指標,並且0x080483e1賦給%eip,程式繼續執行。
---》函數調用執行個體
int swap_add(int* xp,int* yp);int caller(){ int arg1 = 534; int arg2 = 1057; int sum = swap_add(&arg1 , &arg2); int diff = arg1 - arg2; retur sum * diff;}int swap_add(int* xp,int* yp){ int x = * xp; int y = * yp; *xp = y; *yp = x; return x + y;}
(藍色箭頭是“指向”,紅色箭頭是“位移量”,綠色箭頭是解釋說明)
arg1和arg2必須存放在棧中,因為我們必須為它們產生地址。swap_add中的變數int x和int y可以存放在寄存器中。
分配在棧上的24個位元組,8個用於局部變數,8個用於參數,8個未使用,這是因為GCC認識所有的棧空間都應該是16的整數倍。這樣保證資料放的嚴格對齊。
經過調用swap_add之後棧的資訊又恢複到最初的狀態。
---》許多函數編譯後不需要棧幀。如果所有的局部變數都能儲存在寄存器中,而且這個函數又不會調用其他函數(葉子過程),那麼需要棧的唯一原因就是用來儲存傳回值。特別是dui'yu所以,雖然C語言中有寄存器變數,但是如果這個函數的變數很少的話,及時不標明這個變數是寄存器,它也會被載入到寄存器中去。(p196)
---》函數需要棧幀的原因有如下幾個:
●局部變數太多,不能都放在寄存器中。
●有些局部變數是數組或者結構。
●函數用&來計算一個局部變數的地址。
●函數必須將棧上的某些參數傳遞給另外一個函數
●在修改一個被調用著儲存寄存器之前,函數需要儲存其他狀態。
---》棧破壞檢測和棧保護p(181)
在C語言中,沒有可靠的方法來防止對數組的越界寫操作。數組越界,是棧溢出後發現這個錯誤然後拋出。
echo是一個函數,存放了char buf[8]的一個局部變數。
思想:在棧幀中任何局部緩衝區與棧狀態之間儲存一個特殊的金絲雀(canary)值,也成為哨兵值(guard value)是在程式每次運行時隨機產生的。因此如果這個哨兵值改變了說明棧溢出了。
---》棧隨機化p(180)
電腦
比如,多次運行下面的代碼,本地變數的地址是不變的。
int main()
{
int local;
printf("local at %p\n",&local);
return 0;
}
一個現實生活中的例子,但是這個例子說的是每次堆上開闢空間可能是一致的。
曾經在做Symbian項目的時候,發現一個不是必現的bug,後來發現是野指標。但是問題是為什麼不是必現呢?是因為Symbian作業系統每次在堆上開闢的空間,在短時間內是一個地址。舉例:假如,ptr這個指標,現在成為野指標了。但是,之後它指向的記憶體又被重新malloc了,等同於ptr指向了新的對象。但是,這個巧合并不是每次複現。
---》將IA32擴充到64位。p(183)
X86-64是AMD提出來,並命名的。現在一般簡寫X64
●通用目的寄存器組從8個擴充到16個。而且名字也變成了%rax,%rbx。其中%rax用來存放傳回值。
●許多程式狀態都儲存在寄存器中,而不是棧上。整形和指標類型的參數通過寄存器傳遞。所以,有些過程根本不需要建立棧。
●如果可能,條件操作作用條件傳送指令實現,會得到比傳統分支代碼更好的效能。
●浮點操作用面向寄存器的指令集來實現,而不是IA32支援的基於棧的方法來實現。
●X86-64沒有幀寄存器。
---》函數指標的值是該函數機器代碼錶示中的第一條指令的地址。(p173)
剩餘的章節繼續新的blog