Contents ...
udn網路城邦
C/C++內存的理解
2015/10/15 21:04
瀏覽75
迴響0
推薦0
引用0
內存要想理解透徹,首先要理解內存編址。即不同的內存條,內存模塊,插到機器上,具體對應的內存地址是多少。


最開始的PC機,IBM PC XT,只有640k內存。IBM是這麽規劃的,最低的128k,是BIOS的地址,畢竟BIOS也是匯編語言,它也需要合法地址,才能被CPU正確運行。


512k~640k,被定義為端口映射地址,即這部分地址,可能對應某一個外設上的地址,方便程序直接訪問設備。其中,最重要的,就是顯卡,當初的顯卡單元都比較小,如單色顯卡只有2k顯存,就映射在這個地址段。


呵呵,當年比爾蓋茨,開了個世紀有名的黃腔,就是家用電腦,640k內存足以。就是這麽來的,可現在呢?你的顯存都不止640k,可見,偉人也有犯渾的時候。


這樣顯然不方便,因為就在幾年後,286時代,內存已經1M了。

這就麻煩了,這1M的第一個128k不準用,中間還有個128k不準用,好端端的連續內存,就被切成兩塊,痛苦啊。


而那會CPU也是笨得可以,16位的CPU,Intel居然在PC XT上面用的是8位地址總線,這下死翹了,所有的內存被分割為64k一塊塊的小塊,叫段。每個程序模塊,必須小於64k,否則沒法跳轉。以前DOS下可以執行的二進制文件分為兩種,com和exe,com就是不能大於64k的,因為它文件格式裏面沒有段修飾,因此,只能一段完成,64k。


這樣,程序員十分的不方便,寫程序,稍微大點的數組,就要考慮分段訪問,沒辦法,數組下標不能超過64k。

另外,對於640k以上的地址訪問,人們想出了一個很笨的方法,把640k~1024k這384k,也切成一塊一塊的,每塊映射相同的內存區域,靠一個IO切換來訪問不同的塊。那會還沒有想到更好的支持1M以上內存的方法,只能這麽辦。


這樣,程序員成了個苦惱的職業,既要做軟件編程,還要隨時關註自己的數據是否越界,痛苦死了。當然,程序員也不會坐以待斃,這期間,他們自己想了很多方法,比如用底層模塊來解決段切換問題,對上提供一層連續編址的虛擬地址來訪問等等。


ok,到了80386,32位了,大家總算長出了一口氣,這個CPU地址總線有32位,可以直接編址4G內存。

但是,問題來了,PC機已經做成這個樣子了。當然,可以重新開發一款計算機,連續編址,4G內存,很爽,只是,這不是PC機了。


這又讓人痛苦死了,明明有能力做連續編址,跨越段界限,但還是不能這麽做,因為要支持老的程序。


程序員又開始想辦法,基本思路就是用虛擬地址來代替實際地址,後來又想到了,既然都是虛擬的,那我們可不可以把一塊磁盤文件也虛擬成內存,這樣,我們給夠4G,這不是更爽,底層再用一定算法來處理動態轉換的效率優化。


這樣,在93年左右吧,有一個很有名的C++語言,Watcom C++出世了。這個語言,是Dos下第一款支持4G內存的C++語言。這是Sybase開發的,它底層使用了一個虛擬內存管理模塊,是另外一家公司開發的,叫Dos /4G,顯然,就是DOS程序使用4G內存的解決方案。


還記得DOOM、C&C,紅警,金庸群俠傳不?當程序一運行,就會顯示一行字“DOS /4GW ...”,這就是Watcom C++寫的遊戲。因為遊戲界是最需要大內存的,貼圖,聲音的處理,都需要大數組,如果分塊使用,程序員太累了,做不下來這麽大的程序。


嗯,DOS/4GW,是DOS 4G的Watcom C++版本,因為真正的模塊要收費的,這個版本是簡化版,只能支持到256M內存,且不支持磁盤虛擬內存,不過不收費。不過,那個時代的應用也夠了。


我以前寫過Watcom C++的程序,呵呵,真的很爽。再也不考慮段指針之類的東東了,爽翻了。


後來就多了,gcc很早就有了的,99年的時候,gcc進攻DOS市場,出了個版本叫DJgpp,比Watcom C++還好用,我一用就愛上了,當時還把它的庫函數手冊翻譯了一遍,算學習了。


不過,這個時間點,Windows95早出來了,Windows98也出來了,因此,DOS程序趨於沒落。


早在Windows 1.1開發的時候(最早一個Windows版本,1.0沒發布),微軟就知道,以後的操作系統要做到程序員友好才有生命力,而內存連續編址,就是最大的程序員友好。


因此,從一開始,Windows就使用了底層的內存支持技術,當Windows 3.1出世,其實Windows下開發程序,段的限制已經不是很明顯了。


到Windows95,微軟更是直接內置了類似於Dos /4GW之類的32位內存管理器,並內部直接包含虛擬內存和物理內存的自動切換算法,因此,從Windows 95以後,大家再開發程序,已經可以使用理論上長達4G的大數組了。


內存爭議,至此告一段落。


現今32位的Windows系統,普遍支持4G內存,但,應用程序的空間只有2G。編址為低端地址,即0~2G的地址,為什麽呢,因為高2G被系統占用,畢竟Windows系統那麽多服務,也要運行,也需要地址空間。


Windows使用了類似切頁的內存控制機制,每個應用程序,有2G的地址空間,上面2G是所有應用程序和系統共用。

呵呵,不止Windows,32位的Linux也有類似的設計。

因此,一個Windows應用程序最大能使用的內存,只有2G。


前面說的Ring0級的系統內核,一般都占用上2G的地址空間在運行。動態鏈接庫dll,控件OCX,在調入內存中,由於要被多個進程看到,進程間重用,也是占用上面2G在運行。


這裏就要說說鉤子了,當我們想對一個應用程序下鉤子,由於我們的程序和要勾的程序不在一個進程空間,因此,我們看到的內存是不一樣的。就是它在20000這個地址單元看到的可能是個FF,而我們看到的可能是個00,因為這僅僅是邏輯地址一樣,物理地址分屬兩個進程空間。

因此,如果要鉤對方的消息,有個問題,鉤到了,咋送回來?

一般的做法就是做個dll做中轉站,鉤子夠到了,調用dll的函數,先存放到高端內存區,然後我們的程序再定時過去取,或者用回調什麽的。

總之,如果要跨進程通訊,兩個進程的共享內存區,一定是建立在高端的內存,就是2G以上的空間。


至於應用程序自己這2G,就看編譯器咋使用了,一般都是,低端為棧空間,我們的函數代碼,每次call一個函數,函數內部新建立的內部變量,是從低端向高處排。

而堆,則是從高處向底處排,啥時候,兩個碰上了,啥時候,內存就滿了,無法申請內存了。


棧又分為基棧和浮動棧,基棧就是編譯期間就分配好了的內存。

全局變量,const的常量,static的變量,函數的代碼,都是這部分。

浮動棧就是運行期間,根據函數,對象調用關系,動態分配的棧,類成員變量,函數內部變量,都是用的浮動棧。


void Func(void)

{

char i=0;

char* pBuffer=malloc(10);

//...

}

這裏面,Func的代碼,在棧空間,其實是在基棧了。2G的最低

int i,這個i,在浮動棧。基站上方,也還是2G底部。

pBuffer指向的內存,由於是malloc,因此是堆空間,在2G的高端。


new和malloc其實是一樣的,都是malloc的,但new支持對象,要自動調用構造函數。

不過這裏也說明一點,new出來的對象,是運行期對象,其內部成員變量,其實是在2G的高端,堆空間裏面。


寫C和C++程序,需要對內存的分配非常敏感,隨時關註自己使用的變量,是屬於編譯期間的基棧,還是運行期的浮動棧,還是堆。


比如,我們要啟動一個線程,這個線程函數,肯定是在基棧了,編譯器就定好的,但是我們希望線程訪問一個運行期的動態地址,比如要傳遞一個參數給它。

我們就不能簡單把一個函數內部的變量地址傳給他,由於是函數內部變量屬於浮動棧,函數返回,浮動棧就自動拆除,而線程啟動是異步運算,就是一個函數啟動線程,很可能這個函數已經返回了,線程還沒有開始運行。

因此,就絕對不能使用函數內部變量給線程傳參,只能使用堆空間,用malloc的一塊地址來傳參數,再由線程函數收到後,free掉。


這是唯一一個,不遵守“誰分配,誰釋放”原則的特例。我把它叫做“遠堆傳參”。

很多初學線程的朋友,線程寫出來就掛掉,就是這個地方出了問題。

但是,程序表面看起來一切正常,呵呵,所以內存很重要,因為它裏面基本上都是隱式bug,很難用肉眼看代碼看出來。
全站分類:知識學習 科學百科
自訂分類:不分類
上一則: Mongodb之replica set運維篇
下一則: shell的基礎

限會員,要發表迴響,請先登入