數組:php
數組是將元素在內存中連續存放,因爲每一個元素佔用內存相同,能夠經過下標迅速訪問數組中任何元素。可是若是要在數組中增長一個元素,須要移動大量元素,在內存中空出一個元素的空間,而後將要增長的元素放在其中。一樣的道理,若是想刪除一個元素,一樣須要移動大量元素去填掉被移動的元素。若是應用須要快速訪問數據,不多或不插入和刪除元素,就應該用數組。html
鏈表:
鏈表剛好相反,鏈表中的元素在內存中不是順序存儲的,而是經過存在元素中的指針聯繫到一塊兒。好比:上一個元素有個指針指到下一個元素,以此類推,直到最後一個元素。若是要訪問鏈表中一個元素,須要從第一個元素開始,一直找到須要的元素位置。可是增長和刪除一個元素對於鏈表數據結構就很是簡單了,只要修改元素中的指針就能夠了。若是應用須要常常插入和刪除元素你就須要用鏈表數據結構了。 linux
數組必須事先定義固定的長度(元素個數),不能適應數據動態地增減的狀況,即在使用數組以前,就必須對數組的大小進行肯定。當數據增長時,可能超出原先定義的元素個數;當數據減小時,形成內存浪費。數組中插入、刪除數據項時,須要移動其它數據項。而鏈表採用動態分配內存的形式實現,能夠適應數據動態地增減的狀況,須要時能夠用new/malloc分配內存空間,不須要時用delete/free將已分配的空間釋放,不會形成內存空間浪費,且能夠方便地插入、刪除數據項。程序員
數組中的數據在內存中是順序存儲的,而鏈表是隨機存儲的。web
數組的隨機訪問效率很高,能夠直接定位,但插入、刪除操做的效率比較低。算法
鏈表在插入、刪除操做上相對數組有很高的效率,而若是要訪問鏈表中的某個元素的話,那就得從表頭逐個遍歷,直到找到所須要的元素爲止,因此,鏈表的隨機訪問效率比數組低。編程
鏈表不存在越界問題,數組有越界問題。數組便於查詢,鏈表便於插入刪除,數組節省空間可是長度固定,鏈表雖然變長可是佔了更多的存儲空間。windows
因此,因爲數組存儲效率高,存儲速度快的優勢,若是須要頻繁訪問數據,不多插入刪除操做,則使用數組;反之,若是頻繁插入刪除,則應使用鏈表。兩者各有用處。設計模式
數組和鏈表的區別整理以下:
數組在內存中連續,長度固定;鏈表不連續,可動態添加。
數組利用下標定位,時間複雜度爲O(1),鏈表定位元素時間複雜度O(n);
數組插入或刪除元素的時間複雜度O(n),鏈表的時間複雜度O(1)數組
進程是具備必定獨立功能的程序關於某個數據集合上的一次運行活動,它是系統進行資源分配和調度的一個獨立單位。例如,用戶運行本身的程序,系統就建立一個進程,併爲它分配資源,包括各類表格、內存空間、磁盤空間、I/O設備等,而後,該進程被放入到進程的就緒隊列,進程調度程序選中它,爲它分配CPU及其它相關資源,該進程就被運行起來。
線程是進程的一個實體,是CPU調度和分派的基本單位,線程本身基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器、一組寄存器和棧),可是它可與同屬一個進程的其餘的線程共享進程所擁有的所有資源。
在沒有實現線程的操做系統中,進程既是資源分配的基本單位,又是調度的基本單位,它是系統中併發執行的單元。而在實現了線程的操做系統中,進程是資源分配的基本單位,但線程是調度的基本單位,線程是系統中併發執行的單元。
具體而言,引入線程,主要有如下4個方面的優勢:
(1) 易於調度。
(2) 提升併發性。經過線程能夠方便有效地實現併發。
(3) 開銷小。建立線程比建立進程要快,所須要的開銷也更少。
(4) 有利於發揮多處理器的功能。經過建立多線程,每一個線程都在一個處理器上運行,從而實現應用程序的並行,使每一個處理器都獲得充分運行。
須要注意的是,儘管線程與進程兩者很類似,但也存在着很大的不一樣,區別以下:
(1) 一個線程一定屬於也只能屬於一個進程;而一個進程能夠擁有多個線程而且至少擁有一個線程。
(2) 屬於一個進程的全部線程共享該進程的全部資源,包括打開的文件、建立的Socket等。不一樣的進程互相獨立。
(3) 線程又被稱爲輕量級進程。進程有進程控制塊,線程也有線程控制塊。但線程控制塊比進程控制塊小得多。線程間切換代價小,進程間切換代價大。
(4) 進程是程序的一次執行,線程能夠理解爲程序中一段程序片斷的執行。
(5) 每一個進程都有獨立的內存空間,而線程共享其所屬進程的內存空間。
進程和線程的主要差異在於它們是不一樣的操做系統資源管理方式。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不一樣執行路徑。線程有本身的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,因此多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行而且又要共享某些變量的併發操做,只能用線程,不能用進程。
常量指針
定義:
又叫常指針,具備只可以讀取內存中數據,卻不可以修改內存中數據的屬性的指針,稱爲指向常量的指針,簡稱常量指針。
關鍵點:
1.常量指針指向的對象不能經過這個指針來修改,但是仍然能夠經過原來的聲明修改;
2.常量指針能夠被賦值爲變量的地址,之因此叫常量指針,是限制了經過這個指針修改變量的值;
3.指針還能夠指向別處,由於指針自己只是個變量,能夠指向任意地址;
4.指向的地址可變,指向的內容不可變
代碼形式:
int const* p; const int* p;
指針常量
定義:
本質是一個常量,而用指針修飾它。指針常量是指指針所指向的位置不能改變,即指針自己是一個常量,可是指針所指向的內容能夠改變。
關鍵點:
1.它是個常量!
2.指針自己是常量,指向的地址不能夠變化,可是指向的地址所對應的內容能夠變化;
代碼形式:
int* const p;
指向常量的常指針
定義:
指向常量的指針常量就是一個常量,且它指向的對象也是一個常量。
關鍵點:
1.一個指針常量,指向的地址不能變;
2.它指向的指針對象且是一個常量,即它指向的對象不能變化,指向的內容不能變;
代碼形式:
const int* const p;
指針數組:首先它是一個數組,數組的元素都是指針,數組佔多少個字節由數組自己決定。它是「儲存指針的數組」的簡稱。
數組指針:首先它是一個指針,它指向一個數組。在32 位系統下永遠是佔4 個字節,至於它指向的數組佔多少字節,不知道。它是「指向數組的指針」的簡稱。
int *p[2]; 首先聲明瞭一個數組,數組的元素是int型的指針。
int (*p)[2]; 聲明瞭一個指針, 指向了一個有兩個int元素的數組。
其實這兩種寫法主要是由於運算符的優先級, 由於[]的優先級比*高。因此第一種寫法,p先和[]結合,因此是一個數組,後與*結合,是指針。後一種寫法同理。
int (*)[10] p2-----也許應該這麼定義數組指針
這裏有個有意思的話題值得探討一下:平時咱們定義指針不都是在數據類型後面加上指針變量名麼?這個指針p2 的定義怎麼不是按照這個語法來定義的呢?也許咱們應該這樣來定義p2:
int (*)[10] p2;
int (*)[10]是指針類型,p2 是指針變量。這樣看起來的確不錯,不過就是樣子有些彆扭。其實數組指針的原型確實就是這樣子的,只不過爲了方便與好看把指針變量p2 前移了而已。你私下徹底能夠這麼理解這點。雖然編譯器不這麼想。
雙擊打開pdf
1. 如何記憶這兩個命令
du Disk Usage
df Disk Free
2.1 du的工做原理
du命令會對待統計文件逐個調用fstat這個系統調用,獲取文件大小。它的數據是基於文件獲取的,因此有很大的靈活性,不必定非要針對一個分區,能夠跨越多個分區操做。若是針對的目錄中文件不少,du速度就會很慢了。
2.2 df的工做原理
df命令使用的是statfs這個系統調用,直接讀取分區的超級塊信息獲取分區使用狀況。它的數據是基於分區元數據的,因此只能針對整個分區。因爲df直接讀取超級塊,因此運行速度不受文件多少影響。
3 du和df不一致狀況模擬
常見的df和du不一致狀況就是文件刪除的問題。當一個文件被刪除後,在文件系統目錄中已經不可見了,因此du就不會再統計它了。然而若是此時還有運行的進程持有這個已經被刪除了的文件的句柄,那麼這個文件就不會真正在磁盤中被刪除,分區超級塊中的信息也就不會更改。這樣df仍舊會統計這個被刪除了的文件。
4 工做中須要注意的地方
(1)當出現du和df差距很大的狀況時,考慮是不是有刪除文件未完成形成的,方法是lsof命令,而後中止相關進程便可。
(2)能夠使用清空文件的方式來代替刪除文件,方式是:echo > myfile.iso。
(3)對於常常發生刪除問題的日誌文件,以更名、清空、刪除的順序操做。
(4)除了rm外,有些命令會間接的刪除文件,如gzip命令完成後會刪除原來的文件,爲了不刪除問題,壓縮前先確認沒有進程打開該文件。
而df命令經過查看文件系統磁盤塊分配圖得出總塊數與剩餘塊數。
文件系統分配其中的一些磁盤塊用來記錄它自身的一些數據,如i節點,磁盤分佈圖,間接塊,超級塊等。這些數據對大多數用戶級的程序來講是不可見的,一般稱爲Meta Data。
du命令是用戶級的程序,它不考慮Meta Data,而df命令則查看文件系統的磁盤分配圖並考慮Meta Data。
主要的區別由如下幾點:
1、管理方式不一樣;
2、空間大小不一樣;
3、可否產生碎片不一樣;
4、生長方向不一樣;
5、分配方式不一樣;
6、分配效率不一樣;
管理方式:對於棧來說,是由編譯器自動管理,無需咱們手工控制;對於堆來講,釋放工做由程序員控制,容易產生memory leak。
空間大小:通常來說在32位系統下,堆內存能夠達到4G的空間,從這個角度來看堆內存幾乎是沒有什麼限制的。可是對於棧來說,通常都是有必定的空間大小的,例如,在VC6下面,默認的棧空間大小是1M(好像是,記不清楚了)。固然,咱們能夠修改:
打開工程,依次操做菜單以下:Project->Setting->Link,在Category 中選中Output,而後在Reserve中設定堆棧的最大值和commit。
注意:reserve最小值爲4Byte;commit是保留在虛擬內存的頁文件裏面,它設置的較大會使棧開闢較大的值,可能增長內存的開銷和啓動時間。
碎片問題:對於堆來說,頻繁的new/delete勢必會形成內存空間的不連續,從而形成大量的碎片,使程序效率下降。對於棧來說,則不會存在這個問題,由於棧是先進後出的隊列,他們是如此的一一對應,以致於永遠都不可能有一個內存塊從棧中間彈出,在他彈出以前,在他上面的後進的棧內容已經被彈出,詳細的能夠參考數據結構,這裏咱們就再也不一一討論了。
生長方向:對於堆來說,生長方向是向上的,也就是向着內存地址增長的方向;對於棧來說,它的生長方向是向下的,是向着內存地址減少的方向增加。
分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,好比局部變量的分配。動態分配由alloca函數進行分配,可是棧的動態分配和堆是不一樣的,他的動態分配是由編譯器進行釋放,無需咱們手工實現。
分配效率:棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,它的機制是很複雜的,例如爲了分配一塊內存,庫函數會按照必定的算法(具體的算法能夠參考數據結構/操做系統)在堆內存中搜索可用的足夠大小的空間,若是沒有足夠大小的空間(多是因爲內存碎片太多),就有可能調用系統功能去增長程序數據段的內存空間,這樣就有機會分到足夠大小的內存,而後進行返回。顯然,堆的效率比棧要低得多。
與其它計算機進行通信的一個應用,它是對應應用程序的通訊服務的。例如,一個沒有通訊功能的字處理程序就不能執行通訊的代碼,從事字處理工做的程序員也不關心OSI的第7層。可是,若是添加了一個傳輸文件的選項,那麼字處理器的程序員就須要實現OSI的第7層。示例:telnet,HTTP,FTP,NFS,SMTP等。
這一層的主要功能是定義數據格式及加密。例如,FTP容許你選擇以二進制或ASCII格式傳輸。若是選擇二進制,那麼發送方和接收方不改變文件的內容。若是選擇ASCII格式,發送方將把文本從發送方的字符集轉換成標準的ASCII後發送數據。在接收方將標準的ASCII轉換成接收方計算機的字符集。示例:加密,ASCII等。
它定義瞭如何開始、控制和結束一個會話,包括對多個雙向消息的控制和管理,以便在只完成連續消息的一部分時能夠通知應用,從而使表示層看到的數據是連續的,在某些狀況下,若是表示層收到了全部的數據,則用數據表明表示層。示例:RPC,SQL等。
這層的功能包括是否選擇差錯恢復協議仍是無差錯恢復協議,及在同一主機上對不一樣應用的數據流的輸入進行復用,還包括對收到的順序不對的數據包的從新排序功能。示例:TCP,UDP,SPX。
這層對端到端的包傳輸進行定義,它定義了可以標識全部結點的邏輯地址,還定義了路由實現的方式和學習的方式。爲了適應最大傳輸單元長度小於包長度的傳輸介質,網絡層還定義瞭如何將一個包分解成更小的包的分段方法。示例:IP,IPX等。
它定義了在單個鏈路上如何傳輸數據。這些協議與被討論的各類介質有關。示例:ATM,FDDI等。
OSI的物理層規範是有關傳輸介質的特性標準,這些規範一般也參考了其餘組織制定的標準。鏈接頭、幀、幀的使用、電流、編碼及光調製等都屬於各類物理層規範中的內容。物理層經常使用多個規範完成對全部細節的定義。示例:Rj45,802.3等。
返回值區別:malloc失敗返回NULL
New失敗會觸發異常。
1)由於使用補碼能夠將符號位和其餘位統一處理,同時,減法也能夠按加法來處理,即若是是補碼錶示的數,無論是加減法都直接用加法運算便可實現。
2)兩個用補碼錶示的數相加時,若是最高位(符號位)有進位,則進位被捨棄。
這樣的運算有兩個好處:
1)使符號位能與有效值部分一塊兒參加運算,從而簡化運算規則。從而能夠簡化運算器的結構,提升運算速度;(減法運算能夠用加法運算表示出來。)
2)加法運算比減法運算更易於實現。使減法運算轉換爲加法運算,進一步簡化計算機中運算器的線路設計。
採用補碼錶示還有另一個緣由,那就是爲了防止0的機器數有兩個編碼。原碼和反碼錶示的0有兩種形式+0和-0,而咱們知道,+0和-0是相同的。這樣,8位的原碼和反碼錶示的整數的範圍就是-127~+127(11111111~01111111),而採用補碼錶示的時候,00000000是+0,即0;10000000再也不是-0,而是-128,這樣,補碼錶示的數的範圍就是-128~+127了,不但增長了一個數得表示範圍,並且還保證了0編碼的惟一性。
Cache——主存
主存——輔存
兩個重載函數必須在下列一個或兩個方面有所區別:
一、函數有不一樣參數。
二、函數有不一樣參數類型,
注:不一樣的返回值,屬於錯誤的函數重複聲明!
C++運算符重載的相關規定以下:
(1)不能改變運算符的優先級;
(2)不能改變運算符的結合型;
(4)不能改變運算符的操做數的個數;
(5)不能建立新的運算符,只有已有運算符能夠被重載;
(6)運算符做用於C++內部提供的數據類型時,原來含義保持不變。
http://wxxweb.blog.163.com/blog/static/1351269002010113185624129/
1、 介紹
本文意在講解靜態連接庫與動態連接庫的建立與使用,在此以前先來對兩者的概念、區別及優缺點進行簡要的闡述。其中大多內容參考相關網絡資料,因爲本人能力有限,不能確保徹底準確無誤,如有誤差之處請不吝指出。文中使用到的代碼均在Visual Studio 2008中編譯經過,若是您使用的IDE與本文不一樣,可根據實際狀況進行相應項目建立與操做。但願本文內容對您有所幫助。
2、 概念定義
1. 分別編譯與連接
大多數高級語言都支持分別編譯(Compiling),程序員能夠顯式地把程序劃分爲獨立的模塊或文件,而後由編譯器(Compiler)對每一個獨立部分分別進行編譯。在編譯以後,由連接器(Linker)把這些獨立編譯單元連接(Linking)到一塊兒。連接方式分爲兩種:
(1) 靜態連接方式:在程序開發中,將各類目標模塊(.OBJ)文件、運行時庫(.LIB)文件,以及常常是已編譯的資源(.RES)文件連接在一塊兒,以便建立Windows的.EXE文件。
(2) 動態連接方式:在程序運行時,Windows把一個模塊中的函數調用連接到庫模塊中的實際函數上的過程。
2. 靜態連接庫與動態連接庫
靜態連接庫(Static Library,簡稱LIB)與動態連接庫(Dynamic Link Library,簡稱DLL)都是共享代碼的方式。若是使用靜態連接庫(也稱靜態庫),則不管你願不肯意,.LIB文件中的指令都會被直接包含到最終生成的.EXE文件中。可是若使用.DLL文件,該.DLL文件中的代碼沒必要被包含在最終的.EXE文件中,.EXE文件執行時能夠「動態」地載入和卸載這個與.EXE文件獨立的.DLL文件。
2.1. 動態連接方式
連接一個DLL有兩種方式:
2.1.1 載入時動態連接(Load-Time Dynamic Linking)
使用載入時動態連接,調用模塊能夠像調用本模塊中的函數同樣直接使用導出函數名調用DLL中的函數。這須要在連接時將函數所在DLL的導入庫連接到可執行文件中,導入庫向系統提供了載入DLL時所需的信息及用於定位DLL函數的地址符號。(至關於註冊,看成API函數來使用,其實API函數就存放在系統DLL當中。)
2.1.2 運行時動態連接(Run-Time Dynamic Linking)
使用運行時動態連接,運行時能夠經過LoadLibrary或LoadLibraryEx函數載入DLL。DLL載入後,模塊能夠經過調用GetProcAddress獲取DLL函數的入口地址,而後就能夠經過返回的函數指針調用DLL中的函數了。如此便可避免導入庫文件了。
2.2. 兩者優勢及不足
2.2.1 靜態連接庫的優勢
(1) 代碼裝載速度快,執行速度略比動態連接庫快;
(2) 只需保證在開發者的計算機中有正確的.LIB文件,在以二進制形式發佈程序時不需考慮在用戶的計算機上.LIB文件是否存在及版本問題,可避免DLL地獄等問題。
2.2.2 動態連接庫的優勢
(1) 更加節省內存並減小頁面交換;
(2) DLL文件與EXE文件獨立,只要輸出接口不變(即名稱、參數、返回值類型和調用約定不變),更換DLL文件不會對EXE文件形成任何影響,於是極大地提升了可維護性和可擴展性;
(3) 不一樣編程語言編寫的程序只要按照函數調用約定就能夠調用同一個DLL函數。
(4) 適用於大規模的軟件開發,使開發過程獨立、耦合度小,便於不一樣開發者和開發組織之間進行開發和測試。
2.2.3 不足之處
(1) 使用靜態連接生成的可執行文件體積較大,包含相同的公共代碼,形成浪費;
(2) 使用動態連接庫的應用程序不是自完備的,它依賴的DLL模塊也要存在,若是使用載入時動態連接,程序啓動時發現DLL不存在,系統將終止程序並給出錯誤信息。而使用運行時動態連接,系統不會終止,但因爲DLL中的導出函數不可用,程序會加載失敗;
(3) 使用動態連接庫可能形成DLL地獄。
2.3. DLL 地獄
DLL 地獄(DLL Hell)是指由於系統文件被覆蓋而讓整個系統像是掉進了地獄。
簡單地講,DLL地獄是指當多個應用程序試圖共享一個公用組件時,如某個DLL或某個組件對象模型(COM)類,所引起的一系列問題。
最典型的狀況是,某個應用程序將要安裝一個新版本的共享組件,而該組件與機器上的現有版本不向後兼容。雖然剛安裝的應用程序運行正常,但原來依賴前一版本共享組件的應用程序也許已沒法再工做。在某些狀況下,問題的原由更加難以預料。好比,當用戶瀏覽某些web站點時會同時下載某個Microsoft ActiveX控件。若是下載該控件,它將替換機器上原有的任何版本的控件。若是機器上的某個應用程序剛好使用該控件,則極可能也會中止工做。
在許多狀況下,用戶須要很長時間纔會發現應用程序已中止工做。結果每每很難記起是什麼時候的機器變化影響到了該應用程序。
這些問題的緣由是應用程序不一樣組件的版本信息沒有由系統記錄或增強。並且,系統爲某個應用程序所作的改變會影響機器上的全部應用程序—如今創建徹底從變化中隔離出來的應用程序並不容易。
在UNIX 系統中,一個進程結束了,可是他的父進程沒有等待(調用wait / waitpid)他, 那麼他將變成一個殭屍進程。 可是若是該進程的父進程已經先結束了,那麼該進程就不會變成殭屍進程, 由於每一個進程結束的時候,系統都會掃描當前系統中所運行的全部進程, 看有沒有哪一個進程是剛剛結束的這個進程的子進程,若是是的話,就由Init 來接管他,成爲他的父進程。
一個進程在調用exit命令結束本身的生命的時候,其實它並無真正的被銷燬,而是留下一個稱爲殭屍進程(Zombie)的數據結構(系統調用exit,它的做用是使進程退出,但也僅僅限於將一個正常的進程變成一個殭屍進程,並不能將其徹底銷燬)。在Linux進程的狀態中,殭屍進程是很是特殊的一種,它已經放棄了幾乎全部內存空間,沒有任何可執行代碼,也不能被調度,僅僅在進程列表中保留一個位置,記載該進程的退出狀態等信息供其餘進程收集,除此以外,殭屍進程再也不佔有任何內存空間。它須要它的父進程來爲它收屍,若是他的父進程沒安裝SIGCHLD信號處理函數調用wait或waitpid()等待子進程結束,又沒有顯是忽略該信號,那麼它就一直保持殭屍狀態,若是這時父進程結束了,那麼init進程自動會接手這個子進程,爲它收屍,它仍是能被清除的。可是若是父進程是一個循環,不會結束,那麼子進程就會一直保持殭屍狀態,這就是爲何系統中有時會有不少的殭屍進程。
進程長時間保持殭屍狀態通常是錯誤的並致使資源泄漏。
1、改寫父進程,要求父進程爲子進程收屍,調用waitpid()函數或者接受處理SIGCHLD信號
2、若是父進程不關心子進程何時結束,那麼能夠用signal(SIGCHLD,SIG_IGN) 通知內核,本身對子進程的結束不感興趣,那麼子進程結束後,內核會回收, 並再也不給父進程發送信號。
3、fork兩次,殺死一級子進程,使得二級子進程成爲孤兒進程,由init接受。可是這裏父進程仍是要處理殺死一級進程併爲他收屍。
一、手動向(kill)父進程發送SIGCHLD信號,若是父進程仍不處理,則
二、殺死父進程,使其成爲孤兒進程,由init接受,init會回收清理。
三大特性:封裝,繼承,多態。
六大原則:單一職責原則,依賴倒轉原則,里氏準則,開放-封閉原則,接口隔離原則,迪米特法則。
就一個類而言,應該只專一於作一件事和僅有一個引發它變化的緣由
SRP優勢:
消除耦合,減少因需求變化引發代碼僵化性臭味
使用SRP注意點:
一、一個合理的類,應該僅有一個引發它變化的緣由,即單一職責;
二、在沒有變化徵兆的狀況下應用SRP或其餘原則是不明智的;
三、在需求實際發生變化時就應該應用SRP等原則來重構代碼;
四、使用測試驅動開發會迫使咱們在設計出現臭味以前分離不合理代碼;
五、若是測試不能迫使職責分離,僵化性和脆弱性的臭味會變得很強烈,那就應該用Facade或Proxy模式對代碼重構;
Software entities(classes,modules,functions,etc.) should be open for extension, but closed for modification。
軟件實體應當對擴展開放,對修改關閉,即軟件實體應當在不修改(在.Net當中可能經過代理模式來達到這個目的)的前提下擴展。
Open for extension:當新需求出現的時候,能夠經過擴展示有模型達到目的。
Close for modification:對已有的二進制代碼,如dll,jar等,則不容許作任何修改。
OCP優勢:
一、下降程序各部分之間的耦合性,使程序模塊互換成爲可能;
二、使軟件各部分便於單元測試,經過編制與接口一致的模擬類(Mock),能夠很容易地實現軟件各部分的單元測試;
三、利於實現軟件的模塊的呼喚,軟件升級時能夠只部署發生變化的部分,而不會影響其它部分;
使用OCP注意點:
1、實現OCP原則的關鍵是抽象;
2、兩種安全的實現開閉原則的設計模式是:Strategy pattern策略模式),Template Methord(模版方法模式);
3、依據開閉原則,咱們儘可能不要修改類,只擴展類,但在有些狀況下會出現一些比較怪異的情況,這時能夠採用幾個類進行組合來完成;
四、將可能發生變化的部分封裝成一個對象,如: 狀態, 消息,,算法,數據結構等等 , 封裝變化是實現"開閉原則"的一個重要手段,如常常發生變化的狀態值,如溫度,氣壓,顏色,積分,排名等等,能夠將這些做爲獨立的屬性,若是參數之間有關係,有必要進行抽象。對於行爲,若是是基本不變的,則能夠直接做爲對象的方法,不然考慮抽象或者封裝這些行爲;
五、在許多方面,OCP是面向對象設計的核心所在。遵循這個原則可帶來面向對象技術所聲稱的巨大好處(靈活性、可重用性以及可維護性)。然而,對於應用程序的每一個部分都肆意地進行抽象並非一個好主意。應該僅僅對程序中呈現出頻繁變化的那部分做出抽象。拒毫不成熟的抽象和抽象自己同樣重要;
子類型必須可以替換它的基類型。LSP又稱里氏替換原則。
對於這個原則,通俗一些的理解就是,父類的方法都要在子類中實現或者重寫。
LSP優勢:
一、保證系統或子系統有良好的擴展性。只有子類可以徹底替換父類,才能保證系統或子系統在運行期內識別子類就能夠了,於是使得系統或子系統有了良好的擴展性。
二、實現運行期內綁定,即保證了面向對象多態性的順利進行。這節省了大量的代碼重複或冗餘。避免了相似instanceof這樣的語句,或者getClass()這樣的語句,這些語句是面向對象所忌諱的。
三、有利於實現契約式編程。契約式編程有利於系統的分析和設計,指咱們在分析和設計的時候,定義好系統的接口,而後再編碼的時候實現這些接口便可。在父類裏定義好子類須要實現的功能,而子類只要實現這些功能便可。
四使用LSP注意點:
一、此原則和OCP的做用有點相似,其實這些面向對象的基本原則就2條:1:面向接口編程,而不是面向實現;2:用組合而不主張用繼承
二、LSP是保證OCP的重要原則
三、這些基本的原則在實現方法上也有個共同層次,就是使用中間接口層,以此來達到類對象的低偶合,也就是抽象偶合!
四、派生類的退化函數:派生類的某些函數退化(變得沒有用處),Base的使用者不知道不能調用f,會致使替換違規。在派生類中存在退化函數並不老是表示違反了LSP,可是當存在這種狀況時,應該引發注意。
五、從派生類拋出異常:若是在派生類的方法中添加了其基類不會拋出的異常。若是基類的使用者不指望這些異常,那麼把他們添加到派生類的方法中就能夠能會致使不可替換性。
一、高層模塊不該該依賴於低層模塊,兩者都應該依賴於抽象。
二、抽象不該該依賴於細節,細節應該依賴於抽象。
DIP優勢:
使用傳統過程化程序設計所建立的依賴關係,策略依賴於細節,這是糟糕的,由於策略受到細節改變的影響。依賴倒置原則使細節和策略都依賴於抽象,抽象的穩定性決定了系統的穩定性。
啓發式規則:
一、任何變量都不該該持有一個指向具體類的指針或者引用
二、任何類都不該該從具體類派生(始於抽象,來自具體)
三、任何方法都不該該覆寫它的任何基類中的已經實現了的方法
使用多個專門的接口比使用單一的總接口要好。
一個類對另一個類的依賴性應當是創建在最小的接口上的。
一個接口表明一個角色,不該當將不一樣的角色都交給一個接口。沒有關係的接口合併在一塊兒,造成一個臃腫的大接口,這是對角色和接口的污染。
「不該該強迫客戶依賴於它們不用的方法。接口屬於客戶,不屬於它所在的類層次結構。」這個說得很明白了,再通俗點說,不要強迫客戶使用它們不用的方法,若是強迫用戶使用它們不使用的方法,那麼這些客戶就會面臨因爲這些不使用的方法的改變所帶來的改變。
實現方法:
一、使用委託分離接口
二、使用多重繼承分離接口
若是兩個類沒必要彼此直接通訊,那麼這兩個類就不該當發生直接的相互做用。若是其中的一個類須要調用另外一個類的某一個方法的話,能夠經過第三者轉發這個調用。
迪米特法則能夠簡單說成:talk only to your immediate friends。
設計模式的門面模式(Facade)和中介模式(Mediator),都是迪米特法則應用的例子。
http://blog.sciencenet.cn/blog-267716-666789.html
http://see.xidian.edu.cn/cpp/html/483.html
定義了指針變量,可是沒有爲指針分配內存,即指針沒有指向一塊合法的內存。淺顯的例子就不舉了,這裏舉幾個比較隱蔽的例子。
一、結構體成員指針未初始化
struct student
{
char *name;
int score;
}stu,*pstu;
intmain()
{
strcpy(stu.name,"Jimy");
stu.score = 99;
return 0;
}
不少初學者犯了這個錯誤還不知道是怎麼回事。這裏定義告終構體變量stu,可是他沒想到這個結構體內部char *name 這成員在定義結構體變量stu 時,只是給name 這個指針變量自己分配了4 個字節。name 指針並無指向一個合法的地址,這時候其內部存的只是一些亂碼。因此在調用strcpy 函數時,會將字符串"Jimy"往亂碼所指的內存上拷貝,而這塊內存name 指針根本就無權訪問,致使出錯。解決的辦法是爲name 指針malloc 一塊空間。
一樣,也有人犯以下錯誤:
intmain()
{
pstu = (struct student*)malloc(sizeof(struct student));
strcpy(pstu->name,"Jimy");
pstu->score = 99;
free(pstu);
return 0;
}
爲指針變量pstu 分配了內存,可是一樣沒有給name 指針分配內存。錯誤與上面第一種狀況同樣,解決的辦法也同樣。這裏用了一個malloc 給人一種錯覺,覺得也給name 指針分配了內存。
二、沒有爲結構體指針分配足夠的內存
intmain()
{
pstu = (struct student*)malloc(sizeof(struct student*));
strcpy(pstu->name,"Jimy");
pstu->score = 99;
free(pstu);
return 0;
}
爲pstu 分配內存的時候,分配的內存大小不合適。這裏把sizeof(struct student)誤寫爲sizeof(struct student*)。固然name 指針一樣沒有被分配內存。解決辦法同上。
三、函數的入口校驗
無論何時,咱們使用指針以前必定要確保指針是有效的。
通常在函數入口處使用assert(NULL != p)對參數進行校驗。在非參數的地方使用if(NULL != p)來校驗。但這都有一個要求,即p 在定義的同時被初始化爲NULL 了。好比上面的例子,即便用if(NULL != p)校驗也起不了做用,由於name 指針並無被初始化爲NULL,其內部是一個非NULL 的亂碼。
assert 是一個宏,而不是函數,包含在assert.h 頭文件中。若是其後面括號裏的值爲假,則程序終止運行,並提示出錯;若是後面括號裏的值爲真,則繼續運行後面的代碼。這個宏只在Debug 版本上起做用,而在Release 版本被編譯器徹底優化掉,這樣就不會影響代碼的性能。
有人也許會問,既然在Release 版本被編譯器徹底優化掉,那Release 版本是否是就徹底沒有這個參數入口校驗了呢?這樣的話那不就跟不使用它效果同樣嗎?
是的,使用assert 宏的地方在Release 版本里面確實沒有了這些校驗。可是咱們要知道,assert 宏只是幫助咱們調試代碼用的,它的一切做用就是讓咱們儘量的在調試函數的時候把錯誤排除掉,而不是等到Release 以後。它自己並無除錯功能。再有一點就是,參數出現錯誤並不是本函數有問題,而是調用者傳過來的實參有問題。assert 宏能夠幫助咱們定位錯誤,而不是排除錯誤。
爲指針分配了內存,可是內存大小不夠,致使出現越界錯誤。
char *p1 = 「abcdefg」;
char *p2 = (char *)malloc(sizeof(char)*strlen(p1));
strcpy(p2,p1);
p1 是字符串常量,其長度爲7 個字符,但其所佔內存大小爲8 個byte。初學者每每忘了字符串常量的結束標誌「\0」。這樣的話將致使p1 字符串中最後一個空字符「\0」沒有被拷貝到p2 中。解決的辦法是加上這個字符串結束標誌符:
char *p2 = (char *)malloc(sizeof(char)*strlen(p1)+1*sizeof(char));
這裏須要注意的是,只有字符串常量纔有結束標誌符。好比下面這種寫法就沒有結束標誌符了:
char a[7] = {‘a’,’b’,’c’,’d’,’e’,’f’,’g’};
另外,不要由於char 類型大小爲1 個byte 就省略sizof(char)這種寫法。這樣只會使你的代碼可移植性降低。
犯這個錯誤每每是因爲沒有初始化的概念或者是覺得內存分配好以後其值天然爲0。未初始化指針變量也許看起來不那麼嚴重,可是它確確實實是個很是嚴重的問題,並且每每出現這種錯誤很難找到緣由。
曾經有一個學生在寫一個windows 程序時,想調用字庫的某個字體。而調用這個字庫須要填充一個結構體。他很天然的定義了一個結構體變量,而後把他想要的字庫代碼賦值給了相關的變量。可是,問題就來了,無論怎麼調試,他所須要的這種字體效果老是不出來。我在檢查了他的代碼以後,沒有發現什麼問題,因而單步調試。在觀察這個結構體變量的內存時,發現有幾個成員的值爲亂碼。就是其中某一個亂碼惹得禍!由於系統會按照這個結構體中的某些特定成員的值去字庫中尋找匹配的字體,當這些值與字庫中某種字體的某些項匹配時,就調用這種字體。可是很不幸,正是由於這幾個亂碼,致使沒有找到相匹配的字體!由於系統並沒有法區分什麼數據是亂碼,什麼數據是有效的數據。只要有數據,系統就理所固然的認爲它是有效的。
也許這種嚴重的問題並很少見,可是也毫不能掉以輕心。因此在定義一個變量時,第一件事就是初始化。你能夠把它初始化爲一個有效的值,好比:
int i = 10;
char *p = (char *)malloc(sizeof(char));
可是每每這個時候咱們還不肯定這個變量的初值,這樣的話能夠初始化爲0 或NULL。
int i = 0;
char *p = NULL;
若是定義的是數組的話,能夠這樣初始化:
int a[10] = {0};
或者用memset 函數來初始化爲0:
memset(a,0,sizeof(a));
memset 函數有三個參數,第一個是要被設置的內存起始地址;第二個參數是要被設置的值;第三個參數是要被設置的內存大小,單位爲byte。這裏並不想過多的討論memset 函數的用法,若是想了解更多,請參考相關資料。
至於指針變量若是未被初始化,會致使if 語句或assert 宏校驗失敗。這一點,上面已有分析。
內存分配成功,且已經初始化,可是操做越過了內存的邊界。這種錯誤常常是因爲操做數組或指針時出現「多1」或「少1」。好比:
int a[10] = {0};
for (i=0; i<=10; i++)
{
a[i] = i;
}
因此,for 循環的循環變量必定要使用半開半閉的區間,並且若是不是特殊狀況,循環變量儘可能從0 開始。
內存泄漏幾乎是很難避免的,無論是老手仍是新手,都存在這個問題。甚至包括windows,Linux 這類軟件,都或多或少有內存泄漏。也許對於通常的應用軟件來講,這個問題彷佛不是那麼突出,重啓一下也不會形成太大損失。可是若是你開發的是嵌入式系統軟件呢?好比汽車制動系統,心臟起搏器等對安全要求很是高的系統。你總不能讓心臟起搏器重啓吧,人家閻王老爺是很是好客的。
會產生泄漏的內存就是堆上的內存(這裏不討論資源或句柄等泄漏狀況),也就是說由malloc 系列函數或new 操做符分配的內存。若是用完以後沒有及時free 或delete,這塊內存就沒法釋放,直到整個程序終止。
一、告老還鄉求良田
怎麼去理解這個內存分配和釋放過程呢?先看下面這段對話:
萬歲爺:愛卿,你爲朕立下了汗馬功勞,想要何賞賜啊?
某功臣:萬歲,黃金白銀,臣視之如糞土。臣年歲已老,欲告老還鄉。臣乞良田千畝以蔭後世,別無他求。
萬歲爺:愛卿,你勞苦功高,卻僅要如此小賞,朕今天就如你所願。戶部劉侍郎,查看湖廣一帶是否還有千畝上等良田不曾封賞。
劉侍郎:長沙尚有五萬餘畝上等良田不曾封賞。
萬歲爺:在長沙撥良田千畝封賞愛卿。愛卿,良田千畝,你欲何用啊?
某功臣:謝萬歲。長沙一帶,適合種水稻,臣想用來種水稻。種水稻須要把田分爲一畝一塊,方便耕種。
。。。。
二、如何使用malloc 函數
不要莫名其妙,其實上面這段小小的對話,就是malloc 的使用過程。malloc 是一個函數,專門用來從堆上分配內存。使用malloc 函數須要幾個要求:
內存分配給誰?這裏是把良田分配給某功臣。
分配多大內存?這裏是分配一千畝。
是否還有足夠內存分配?這裏是還有足夠良田分配。
內存的將用來存儲什麼格式的數據,即內存用來作什麼?
這裏是用來種水稻,須要把田分紅一畝一塊。分配好的內存在哪裏?這裏是在長沙。
若是這五點都肯定,那內存就能分配。下面先看malloc 函數的原型:
(void *)malloc(int size)
malloc 函數的返回值是一個void 類型的指針,參數爲int 類型數據,即申請分配的內存大小,單位是byte。內存分配成功以後,malloc 函數返回這塊內存的首地址。你須要一個指針來接收這個地址。可是因爲函數的返回值是void *類型的,因此必須強制轉換成你所接收的類型。也就是說,這塊內存將要用來存儲什麼類型的數據。好比:
char *p = (char *)malloc(100);
在堆上分配了100 個字節內存,返回這塊內存的首地址,把地址強制轉換成char *類型後賦給char *類型的指針變量p。同時告訴咱們這塊內存將用來存儲char 類型的數據。也就是說你只能經過指針變量p 來操做這塊內存。這塊內存自己並無名字,對它的訪問是匿名訪問。
上面就是使用malloc 函數成功分配一塊內存的過程。可是,每次你都能分配成功嗎?
不必定。上面的對話,皇帝讓戶部侍郎查詢是否還有足夠的良田未被分配出去。使用malloc函數一樣要注意這點:若是所申請的內存塊大於目前堆上剩餘內存塊(整塊),則內存分配會失敗,函數返回NULL。注意這裏說的「堆上剩餘內存塊」不是全部剩餘內存塊之和,由於malloc 函數申請的是連續的一塊內存。
既然malloc 函數申請內存有不成功的可能,那咱們在使用指向這塊內存的指針時,必須用if(NULL != p)語句來驗證內存確實分配成功了。
三、用malloc 函數申請0 字節內存
另外還有一個問題:用malloc 函數申請0 字節內存會返回NULL 指針嗎?
能夠測試一下,也能夠去查找關於malloc 函數的說明文檔。申請0 字節內存,函數並不返回NULL,而是返回一個正常的內存地址。可是你卻沒法使用這塊大小爲0 的內存。這好尺子上的某個刻度,刻度自己並無長度,只有某兩個刻度一塊兒才能量出長度。對於這一點必定要當心,由於這時候if(NULL != p)語句校驗將不起做用。
四、內存釋放
既然有分配,那就必須有釋放。否則的話,有限的內存總會用光,而沒有釋放的內存卻在空閒。與malloc 對應的就是free 函數了。free 函數只有一個參數,就是所要釋放的內存塊的首地址。好比上例:
free(p);
free 函數看上去挺狠的,但它到底做了什麼呢?其實它就作了一件事:斬斷指針變量與這塊內存的關係。好比上面的例子,咱們能夠說malloc 函數分配的內存塊是屬於p 的,由於咱們對這塊內存的訪問都須要經過p 來進行。free 函數就是把這塊內存和p 之間的全部關係斬斷。今後p 和那塊內存之間再無瓜葛。至於指針變量p 自己保存的地址並無改變,可是它對這個地址處的那塊內存卻已經沒有全部權了。那塊被釋放的內存裏面保存的值也沒有改變,只是再也沒有辦法使用了。
這就是free 函數的功能。按照上面的分析,若是對p 連續兩次以上使用free 函數,確定會發生錯誤。由於第一使用free 函數時,p 所屬的內存已經被釋放,第二次使用時已經無內存可釋放了。關於這點,我上課時讓學生記住的是:必定要一夫一妻制,否則確定出錯。
malloc 兩次只free 一次會內存泄漏;malloc 一次free 兩次確定會出錯。也就是說,在程序中malloc 的使用次數必定要和free 相等,不然必有錯誤。這種錯誤主要發生在循環使用malloc 函數時,每每把malloc 和free 次數弄錯了。這裏留個練習:
寫兩個函數,一個生成鏈表,一個釋放鏈表。兩個函數的參數都只使用一個表頭指針。
五、內存釋放以後
既然使用free 函數以後指針變量p 自己保存的地址並無改變,那咱們就須要從新把p的值變爲NULL:
p = NULL;
這個NULL 就是咱們前面所說的「栓野狗的鏈子」。若是你不栓起來早晚會出問題的。好比:
在free(p)以後,你用if(NULL != p)這樣的校驗語句還能起做用嗎?例如:
char *p = (char *)malloc(100);
strcpy(p, 「hello」);
free(p); /* p 所指的內存被釋放,可是p 所指的地址仍然不變*/
…
if (NULL != p)
{
/* 沒有起到防錯做用*/
strcpy(p, 「world」); /* 出錯*/
}
釋放完塊內存以後,沒有把指針置NULL,這個指針就成爲了「野指針」,也有書叫「懸垂指針」。這是很危險的,並且也是常常出錯的地方。因此必定要記住一條:free 完以後,必定要給指針置NULL。
同時留一個問題:對NULL 指針連續free 屢次會出錯嗎?爲何?若是讓你來設計free函數,你會怎麼處理這個問題?
這裏通常有三種狀況:
第一種:就是上面所說的,free(p)以後,繼續經過p 指針來訪問內存。解決的辦法就是給p 置NULL。
第二種:函數返回棧內存。這是初學者最容易犯的錯誤。好比在函數內部定義了一個數組,卻用return 語句返回指向該數組的指針。解決的辦法就是弄明白棧上變量的生命週期。
第三種:內存使用太複雜,弄不清到底哪塊內存被釋放,哪塊沒有被釋放。解決的辦法是從新設計程序,改善對象之間的調用關係。
http://blog.csdn.net/hzhzh007/article/details/6424638
http://blog.163.com/xychenbaihu@yeah/blog/static/132229655201210975312473/
如何查看進程發生缺頁中斷的次數?
用ps -o majflt,minflt -C program命令查看。
majflt表明major fault,中文名叫大錯誤,minflt表明minor fault,中文名叫小錯誤。
這兩個數值表示一個進程自啓動以來所發生的缺頁中斷的次數。
發成缺頁中斷後,執行了那些操做?
當一個進程發生缺頁中斷的時候,進程會陷入內核態,執行如下操做:
1、檢查要訪問的虛擬地址是否合法
2、查找/分配一個物理頁
3、填充物理頁內容(讀取磁盤,或者直接置0,或者啥也不幹)
4、創建映射關係(虛擬地址到物理地址)
從新執行發生缺頁中斷的那條指令
若是第3步,須要讀取磁盤,那麼此次缺頁中斷就是majflt,不然就是minflt。
內存分配的原理
從操做系統角度來看,進程分配內存有兩種方式,分別由兩個系統調用完成:brk和mmap(不考慮共享內存)。
1、brk是將數據段(.data)的最高地址指針_edata往高地址推;
2、mmap是在進程的虛擬地址空間中(堆和棧中間,稱爲文件映射區域的地方)找一塊空閒的虛擬內存。
這兩種方式分配的都是虛擬內存,沒有分配物理內存。在第一次訪問已分配的虛擬地址空間的時候,發生缺頁中斷,操做系統負責分配物理內存,而後創建虛擬內存和物理內存之間的映射關係。
在標準C庫中,提供了malloc/free函數分配釋放內存,這兩個函數底層是由brk,mmap,munmap這些系統調用實現的。
下面以一個例子來講明內存分配的原理:
狀況1、malloc小於128k的內存,使用brk分配內存,將_edata往高地址推(只分配虛擬空間,不對應物理內存(所以沒有初始化),第一次讀/寫數據時,引發內核缺頁中斷,內核才分配對應的物理內存,而後虛擬地址空間創建映射關係),以下圖:
1、進程啓動的時候,其(虛擬)內存空間的初始佈局如圖1所示。
其中,mmap內存映射文件是在堆和棧的中間(例如libc-2.2.93.so,其它數據文件等),爲了簡單起見,省略了內存映射文件。
_edata指針(glibc裏面定義)指向數據段的最高地址。
2、進程調用A=malloc(30K)之後,內存空間如圖2:
malloc函數會調用brk系統調用,將_edata指針往高地址推30K,就完成虛擬內存分配。
你可能會問:只要把_edata+30K就完成內存分配了?
事實是這樣的,_edata+30K只是完成虛擬地址的分配,A這塊內存如今仍是沒有物理頁與之對應的,等到進程第一次讀寫A這塊內存的時候,發生缺頁中斷,這個時候,內核才分配A這塊內存對應的物理頁。也就是說,若是用malloc分配了A這塊內容,而後歷來不訪問它,那麼,A對應的物理頁是不會被分配的。
3、進程調用B=malloc(40K)之後,內存空間如圖3。
狀況2、malloc大於128k的內存,使用mmap分配內存,在堆和棧之間找一塊空閒內存分配(對應獨立內存,並且初始化爲0),以下圖:
4、進程調用C=malloc(200K)之後,內存空間如圖4:
默認狀況下,malloc函數分配內存,若是請求內存大於128K(可由M_MMAP_THRESHOLD選項調節),那就不是去推_edata指針了,而是利用mmap系統調用,從堆和棧的中間分配一塊虛擬內存。
這樣子作主要是由於::
brk分配的內存須要等到高地址內存釋放之後才能釋放(例如,在B釋放以前,A是不可能釋放的,這就是內存碎片產生的緣由,何時緊縮看下面),而mmap分配的內存能夠單獨釋放。
固然,還有其它的好處,也有壞處,再具體下去,有興趣的同窗能夠去看glibc裏面malloc的代碼了。
5、進程調用D=malloc(100K)之後,內存空間如圖5;
6、進程調用free(C)之後,C對應的虛擬內存和物理內存一塊兒釋放。
7、進程調用free(B)之後,如圖7所示:
B對應的虛擬內存和物理內存都沒有釋放,由於只有一個_edata指針,若是往回推,那麼D這塊內存怎麼辦呢?
固然,B這塊內存,是能夠重用的,若是這個時候再來一個40K的請求,那麼malloc極可能就把B這塊內存返回回去了。
8、進程調用free(D)之後,如圖8所示:
B和D鏈接起來,變成一塊140K的空閒內存。
9、默認狀況下:
當最高地址空間的空閒內存超過128K(可由M_TRIM_THRESHOLD選項調節)時,執行內存緊縮操做(trim)。在上一個步驟free的時候,發現最高地址空閒內存超過128K,因而內存緊縮,變成圖9所示。
http://blog.csdn.net/dog250/article/details/5302958
本質上貝爾實驗室的malloc使用了一個線性的鏈表來表示能夠分配的內存塊,熟悉夥伴系統和slab的應該知道這是這麼回事,可是仍然和slab和夥伴系統有所不一樣,slab中分配的都是相同大小的內存塊,而夥伴系統中分配的是不一樣肯定大小的內存塊,好比肯定的1,2,4,8...的內存,貝爾試驗室的malloc版本是一種隨意的分配,自己遵循碎片最少化,利用率最大化的原則分配,其實想一想slab的初衷,其實就是一個池的概念,省去了分配時的開銷,而夥伴系統的提出就是爲了消除碎片,貝爾malloc版本一箭雙鵰。
typedef struct mem{
struct mem *next;//巧妙之處
Unsigned len;
}mem;
設F爲mem指針,&F並非一個真正的mem指針,而是因爲mem的第一個字段爲一個mem指針,而&F在內存中應該是一個mem指針的指針,可是該指針的指針不論如何也是一個指針類型,其指向的數據正好也是一個指針,後者是mem指針類型,這正好符合mem結構體的佈局,mem結構體的第一個字段就是一個mem指針類型,所以咱們能夠將&F理解成F的前一個元素,由於&F的第一個字段是F
http://blog.csdn.net/vanbreaker/article/details/7605367
1、夥伴關係主要用來解決外部碎片問題。
夥伴系統的宗旨就是用最小的內存塊來知足內核的對於內存的請求。在最初,只有一個塊,也就是整個內存,假如爲1M大小,而容許的最小塊爲64K,那麼當咱們申請一塊200K大小的內存時,就要先將1M的塊分裂成兩等分,各爲512K,這兩分之間的關係就稱爲夥伴,而後再將第一個512K的內存塊分裂成兩等分,各位256K,將第一個256K的內存塊分配給內存,這樣就是一個分配的過程。
2、slab用來解決內部碎片問題,通常直接工做在夥伴系統上。
http://oss.org.cn/kernel-book/ch06/6.3.3.htm
slab是Linux操做系統的一種內存分配機制。其工做是針對一些常常分配並釋放的對象,如進程描述符等,這些對象的大小通常比較小,若是直接採用夥伴系統來進行分配和釋放,不只會形成大量的內碎片,並且處理速度也太慢。而slab分配器是基於對象進行管理的,相同類型的對象歸爲一類(如進程描述符就是一類),每當要申請這樣一個對象,slab分配器就從一個slab列表中分配一個這樣大小的單元出去,而當要釋放時,將其從新保存在該列表中,而不是直接返回給夥伴系統,從而避免這些內碎片。slab分配器並不丟棄已分配的對象,而是釋放並把它們保存在內存中。當之後又要請求新的對象時,就能夠從內存直接獲取而不用重複初始化。
http://blog.csdn.net/w_miracle/article/details/12321819
http://www.cppblog.com/weiym/archive/2012/05/05/173785.html
http://www.cnblogs.com/bangerlee/archive/2011/08/31/2161421.html
http://2309998.blog.51cto.com/2299998/1263164
在軟件開發中,有些對象使用很是頻繁,那麼咱們能夠預先在堆中實例化一些對象,咱們把維護這些對象的結構叫「內存池」。在須要用的時候,直接從內存池中拿,而不用重新實例化,在要銷燬的時候,不是直接free/delete,而是返還給內存池。
把那些經常使用的對象存在內存池中,就不用頻繁的分配/回收內存,能夠相對減小內存碎片,更重要的是實例化這樣的對象更快,回收也更快。當內存池中的對象不夠用的時候就擴容。
C/C++下內存管理是讓幾乎每個程序員頭疼的問題,分配足夠的內存、追蹤內存的分配、在不須要的時候釋放內存——這個任務至關複雜。而直接使用系統調用malloc/free、new/delete進行內存分配和釋放,有如下弊端:
1、調用malloc/new,系統須要根據「最早匹配」、「最優匹配」或其餘算法在內存空閒塊表中查找一塊空閒內存,調用free/delete,系統可能須要合併空閒內存塊,這些會產生額外開銷
2、頻繁使用時會產生大量內存碎片,從而下降程序運行效率
3、容易形成內存泄漏
內存池(memory pool)是代替直接調用malloc/free、new/delete進行內存管理的經常使用方法,當咱們申請內存空間時,首先到咱們的內存池中查找合適的內存塊,而不是直接向操做系統申請,優點在於:
1、比malloc/free進行內存申請/釋放的方式快
2、不會產生或不多產生堆碎片
3、可避免內存泄漏
http://blog.csdn.net/liu1pan2min3/article/details/8545979
所以線程池的出現正是着眼於減小線程池自己帶來的開銷。線程池採用預建立的技術,在應用程序啓動以後,將當即建立必定數量的線程(N1),放入空閒隊列中。這些線程都是處於阻塞(Suspended)狀態,不消耗CPU,但佔用較小的內存空間。當任務到來後,緩衝池選擇一個空閒線程,把任務傳入此線程中運行。當N1個線程都在處理任務後,緩衝池自動建立必定數量的新線程,用於處理更多的任務。在任務執行完畢後線程也不退出,而是繼續保持在池中等待下一次的任務。當系統比較空閒時,大部分線程都一直處於暫停狀態,線程池自動銷燬一部分線程,回收系統資源。
基於這種預建立技術,線程池將線程建立和銷燬自己所帶來的開銷分攤到了各個具體的任務上,執行次數越多,每一個任務所分擔到的線程自己開銷則越小,不過咱們另外可能須要考慮進去線程之間同步所帶來的開銷。
構建線程池框架
通常線程池都必須具有下面幾個組成部分:
1、線程池管理器:用於建立並管理線程池
2、工做線程: 線程池中實際執行的線程
3、任務接口: 儘管線程池大多數狀況下是用來支持網絡服務器,可是咱們將線程執行的任務抽象出來,造成任務接口,從而是的線程池與具體的任務無關。
4、任務隊列:線程池的概念具體到實現則多是隊列,鏈表之類的數據結構,其中保存執行線程
http://www.360doc.com/content/12/1008/19/1317564_240288359.shtml
任何一個程序一般都包括代碼段和數據段,這些代碼和數據自己都是靜態的。程序要想運行,首先要由操做系統負責爲其建立進程,並在進程的虛擬地址空間中爲其代碼段和數據段創建映射。光有代碼段和數據段是不夠的,進程在運行過程當中還要有其動態環境,其中最重要的就是堆棧。圖3所示爲Linux下進程的地址空間佈局:
圖3 Linux下進程地址空間的佈局
首先,execve(2)會負責爲進程代碼段和數據段創建映射,真正將代碼段和數據段的內容讀入內存是由系統的缺頁異常處理程序按需完成的。另外,execve(2)還會將bss段清零,這就是爲何未賦初值的全局變量以及static變量其初值爲零的緣由。進程用戶空間的最高位置是用來存放程序運行時的命令行參數及環境變量的,在這段地址空間的下方和bss段的上方還留有一個很大的空洞,而做爲進程動態運行環境的堆棧和堆就棲身其中,其中堆棧向下伸展,堆向上伸展。
知道了堆棧在進程地址空間中的位置,咱們再來看一看堆棧中都存放了什麼。相信讀者對C語言中的函數這樣的概念都已經很熟悉了,實際上堆棧中存放的就是與每一個函數對應的堆棧幀。當函數調用發生時,新的堆棧幀被壓入堆棧;當函數返回時,相應的堆棧幀從堆棧中彈出。典型的堆棧幀結構如圖4所示。
堆棧幀的頂部爲函數的實參,下面是函數的返回地址以及前一個堆棧幀的指針,最下面是分配給函數的局部變量使用的空間。一個堆棧幀一般都有兩個指針,其中一個稱爲堆棧幀指針,另外一個稱爲棧頂指針。前者所指向的位置是固定的,然後者所指向的位置在函數的運行過程當中可變。所以,在函數中訪問實參和局部變量時都是以堆棧幀指針爲基址,再加上一個偏移。對照圖4可知,實參的偏移爲正,局部變量的偏移爲負。
圖4 典型的堆棧幀結構
http://blog.csdn.net/zsy2020314/article/details/9429707
http://www.cnblogs.com/bangerlee/archive/2012/05/22/2508772.html