第一部分:溫故而知新
第一章:介紹基本的背景知識——作業系統、執行緒、硬體
1、關於c語言中的hello world這些問題你都清楚嗎?
2、計算機硬體裝置的三個核心部件:
1》**處理器cpu
2》記憶體
3》i/o控制晶元
4、作業系統的功能:
1》提供抽象介面。
2》管理硬體資源
5、多道程式——》分時系統——》多工系統(cpu採用搶占式分配方式)
6、執行緒:
1》執行緒,有時候被稱為輕量級程序(lwp),是程式執行流得最小單元。
2》乙個標準的執行緒由執行緒id、當前指標指令(pc)、暫存器集合、堆疊組成
3》通常,乙個程序由乙個到多個執行緒組成,各執行緒之間共享程式的記憶體空間(包括**段、資料段、堆等)以及一些程序級的資源(如開啟檔案和訊號)
7、二元訊號量:
1》二元訊號量是一種最簡單的鎖。它只有兩種狀態:占用和非占用。
2》它適合只能被唯一乙個執行緒獨佔訪問的資源,當二元訊號處於非占用狀態時,第乙個試圖獲取該二元訊號量的執行緒會獲取該鎖,並將二元訊號量置為占用狀態,以後其他的所有試圖獲取該二元訊號量的執行緒將會被等待,直到該鎖被釋放。
8、volatile關鍵字:
1》阻止編譯器為了提高速度將乙個變數快取到暫存器內而不寫回。
2》阻止編譯器調整volatila變數的指令順序
3》volatile雖然能夠阻止編譯器調整順序,但是無法阻止cpu動態排程換序。
8+、單例模式:singleton
1》單例模式是一種常用的軟體設計模式。在它的核心結構中只包含乙個被稱為單例的特殊類。
2》通過單例模式可以保證系統中乙個類只有乙個例項而且該例項易於外界訪問,從而方便對例項個數的控制並節約資源。
3》如果希望在系統中的某個類的物件只能存在乙個,單例模式是最好的解決方案。
9、乙個類只能建立乙個物件
class singleton
~singleton()
private:
int data;
static singleton* pobj;
static std::mutex _mutex;
};singleton* singleton::pobj
=null;
std::mutex _mutex;
singleton* createobj()
_mutex.unlock();
}return singleton::pobj;
}void releaseobj()
}
10、另乙個著名的與換序有關的問題:singleton模式的double-check.
一段經典的**:
volatile t* pinst=0;
t* getinstance( )
unlock( );
}return pinst;
}
——當函式返回時,pinst總是指向乙個有效地物件。而lock 和 unlock防止了多執行緒競爭的問題。
——雙重if可以讓lock的呼叫開銷降低到最小。
但是:實際上這段**是有問題的,問題**於cpu的亂序執行。
1》c++裡的new包含了兩個步驟:分配記憶體 、 呼叫建構函式
2》所以pinst = new t也包含了三個步驟:1分配記憶體 、2在記憶體的位置上呼叫建構函式 、3 將記憶體的位址賦值給pinst。
3》在上面的三步中,2 3 的順序是可以顛倒的。也就是說完全有可能出現這樣的情況:pinst的值已經不是null了,但是物件還沒有構造完。這時候,如果出現另外乙個對getinstance的併發呼叫,此時第乙個if內的表示式pinst == null 為false,所以這個呼叫會直接返回尚未構造完的物件的位址已提供給使用者使用,那麼程式就有可能崩潰。
11、記憶體屏障——barrier指令
1》記憶體屏障,也稱記憶體柵欄、屏障指令,是一類同步屏障指令,使得cpu或編譯器在對記憶體隨機訪問的操作中的乙個同步點,使得此點之前的所有讀寫操作都執行後才可以開始執行此點之後的操作。
2》大多數現代計算機為了提高效能而採取的亂序執行,這使得記憶體屏障成為必須。
3》語義上:記憶體屏障前的所有寫操作都要寫入記憶體;記憶體屏障之後的讀操作都可以獲得同步屏障之前的寫操作的結果。
4》因此,對於敏感的程式塊,寫操作之後、讀操作之前可以插入記憶體屏障。
12、許多體系結構的cpu都提供barrier指令,不過它們的名稱各不相同,例如powerpc提供的其中一條指令名叫lwaync。
我們可以這樣保證執行緒安全:
#define barrier( ) _asm_ volatile ( "lwaync")
volatile t* pinst=0;
t* getinstance( )
unlock( );
}return pinst;
}
——由於barrier的存在,物件的構造一定在barrier執行之前完成,因此當pinst被賦值時物件總是完整的。
1、乙個 hello world 程式 需要完成四個步驟:分別是 預處理(prepressing) 、編譯(compilation) 、彙編 (assembly)、和鏈結(linking)
3、預編譯:
1》首先是源**檔案helloc.c和相關的標頭檔案,如stdio.h等被預編譯器cpp預編譯成乙個 .i 檔案。對於c++程式來說,它的源**檔案的副檔名可能是 .cpp 或 .cxx ,標頭檔案的副檔名可能是 .hpp ,而預編譯後的檔案按副檔名是 .ii 。
2》第一步預編譯的過程相當於如下的命令(-e表示只進行預編譯): gc
c−eh
ello
.c−o
hell
o.i或
者 gcc hello.c > hello.i
3》預編譯過程主要處理那些原始檔**中的以「#」開始的預編譯指令,比如「#include」、「#define」等,主要處理規則如下:
1>將所有「#define」刪除,並展開所有的巨集定義。
2>處理所有條件預編譯指令,比如:「if」、「ifdef」、「elif」、「else」、「endif」
3>處理「#include」預編譯指令,將被包含的檔案插入到該預編譯指令的位置。注意,這個過程是遞迴進行的,也就是說被包含的檔案可能還包含其他檔案。
4>刪除所有的注釋「//」和「/* */」.
5>新增行號和檔案標識,比如#2 「hello.c」 2,以便於編譯時編譯器產生除錯用的行號資訊以及用於編譯時產生的編譯錯誤或者警告時能夠顯示行號。
6>保留所有#pragma 編譯器指令,因為編譯器需要使用它們。
4》經過預編譯之後的 .i 檔案不包含任何巨集定義,因為所有的巨集都已經被展開了,並且包含的檔案也已經被插入到 .i 檔案中。所以當我們無法判斷巨集定義是否正確或標頭檔案包含是否正確時,可以檢視預編譯後的檔案來確定問題。
4、編譯:
1》編譯過程就是把預處理完的檔案進行一系列詞法分析、語法分析、語義分析及優化後產生相應的彙編**檔案,這個過程往往是我們所說的整個程式構建的核心部分,也是最複雜的部分之一。
2》上面的編譯過程相當於如下命令:gc
c−sh
ello
.i−o
hell
o.s或
者 gcc -s hello.o -o hello.s
3》實際上gcc這個命令只是這些後台程式的包裝,它會根據不同的引數要求去呼叫預編譯編譯程式cc1、彙編器as、鏈結器ld
5、彙編:
1》彙編器是將彙編**轉變成機器可以執行的指令,每乙個彙編語句幾乎都對應一條機器指令。所以彙編器的彙編過程相對於彙編器來講比較簡單,它沒有複雜的語法,也沒有語義,也不需要指令優化,只是根據彙編指令和機器指令的對照表一一翻譯就可以了。
2》上面的彙編過程我們可以呼叫彙編器as來完成: as
hell
o.s−
ohel
lo.o
或者gcc -c hello.s -o hello.o 或者使用gcc命令從c源**檔案開始,經過預編譯、編譯、彙編直接輸出目標檔案 $gcc -c hello.c -o hello.o
7、編譯器:
編譯器的作用:從最直觀的角度來講,編譯器就是將高階語言翻譯成機器語言的乙個工具。比如我們用c/c++語言寫的乙個程式可以使用編譯器將其翻譯成機器可以執行的指令及資料。
8、編譯過程:
1》編譯過程一般分為6步:掃瞄、詞法分析、語義分析、源**優化、**生成、和目標**優化
2》整個過程如下圖:
9、詞法分析:首先源**程式被輸入到掃面器(scanner),掃瞄器的任務很簡單,它只是簡單的進行詞法分析,運用一種類似於有限狀態機(finite state machine)的演算法可以很輕鬆的將源**的字串行分割成一系列的記號(token)。
10、語法分析:接下來語法分析器(frammar parser)將對掃瞄器產生的記號進行語法分析,從而產生了語法樹。整個分析過程採用了上下文物管語法的分析手段。
11、語義分析:語義分析是由語義分析器來完成的。
《程式設計師的自我修養》讀書筆記
1 最佳實踐 作為一名程式設計師,你也需要嘗試去理解那些軟體領域最本質的東西,而我的建議就是學習那些最佳實踐。最佳實踐 bestpractice 是乙個管理學概念,即 可使結果最優,並減少出錯可能性的某種技術或方法。最佳實踐一定是要經受住實踐檢驗才得出的。學習本質 是 以慢打快 的策略,但這種策略短...
程式設計師的自我修養 讀書筆記
哦,笑吧,科廷,老夥計。這是上帝,或者也可以說是命運或自然,跟我們開的乙個玩笑。不過,不管這傢伙是誰或是什麼,他真幽默,哈哈!霍華德,碧血沙金 專業主義不但象徵著榮譽與驕傲,而且明確意味著責任與義務。假如不小心在程式中寫了乙個bug,以致於公司損失10萬,對於非專業人士來說,只會聳聳肩 狀況總是難免...
程式設計師的自我修養 讀書筆記
1 注意不要反回指向棧記憶體的指標或引用,因為在函式返回時改記憶體已經被銷毀了 2 c c 沒有辦法知道指標所指的記憶體容量大小 當陣列作為引數傳遞時,陣列將退化成相同型別的指標 不要指望要指標引數去申請動態記憶體,因為函式會為產生乙個臨時變數指向引數的記憶體,當函式內分配記憶體時,將記憶體的位址賦...