精選分類 書庫 完本 排行 原創專區
欣可小說 > 曆史 > 水晶頭 > ◎第一章 內存 內存管理

水晶頭 ◎第一章 內存 內存管理

作者:陳忠實 分類:曆史 更新時間:2026-07-24 07:10:02

{

\"code\": 200,

\"title\": \"\",

\"content\": \"內存管理,是指軟件運行時對計算機內存資源的分配和使用的技術。其最主要的目的是如何高效,快速的分配,並且在適當的時候釋放和回收內存資源。一個執行中的程式,譬如網頁瀏覽器在個人電腦或是圖靈機(Turingmachine)裡麵,為一個行程將資料轉換於真實世界及電腦內存之間,然後將資料存於電腦內存內部(在計算機科學,一個程式是一群指令的集合,一個行程是電腦在執行中的程式)。一個程式結構由以下兩部分而成:“本文區段”,也就是指令存放,提供CPU使用及執行;“資料區段”,儲存程式內部本身設定的資料,例如常數字串。\\n\\n內存可以通過許多媒介實現,例如磁帶或是磁盤,或是小陣列容量的微晶片。從1950年代開始,計算機變的更複雜,它內部由許多種類的內存組成。內存管理的任務也變的更加複雜,甚至必須在一台機器同時執行多個進程。\\n\\n虛擬內存是內存管理技術的一個極其實用的創新。它是一段程式(由操作係統調度),持續監控著所有物理內存中的代碼段、數據段,並保證他們在運行中的效率以及可靠性,對於每個用戶層(user-level)的進程分配一段虛擬內存空間。當進程建立時,不需要在物理內存件之間搬移數據,數據儲存於磁盤內的虛擬內存空間,也不需要為該進程去配置主內存空間,隻有當該進程被被調用的時候纔會被加載到主內存。\\n\\n可以想像一個很大的程式,當他執行時被操作係統調用,其運行需要的內存數據都被存到磁盤內的虛擬內存,隻有需要用到的部分才被加載到主內存內部運行。\\n\\n主內存\\n\\n當一個程式執行,操作係統將程式的資料區段及本文區段對映到虛擬內存空間內部,然後在內存執行程式的指令(見馮諾依曼架構(vonNeumannarchitecture),無論如何,當程式執行時就必須去儲存暫時性的資料,或更重要的,它會呼叫一些函式(function)或是例程(subroutine),並且儲存當前函式的狀態,最好的數據結構方法,資料由堆棧(stack)的方式儲存,當我們完成這個函式,資料會由堆棧的pop方式取出,堆棧將會在函式的生命週期內動態的成長,操作係統提供區分本文區段及資料區段,而堆棧區段則在一個行程的最頂端,這種方式稱為段式結構(segments)或“分段”。\\n\\n內存管理\\n\\n內存管理對於編寫出高效率的Windows程式是非常重要的,這是因為Windows是多任務係統,它的內存管理和單任務的DOS相比有很大的差異。DOS是單任務操作係統,應用程式分配到內存後,如果它不主動釋放,係統是不會對它作任何改變的;但Windows卻不然,它在同一時刻可能有多個應用程式共享內存,有時為了使某個任務更好地執行,Windows係統可能會對其它任務分配的內存進行移動,甚至刪除。因此,我們在Windows應用程式中使用內存時,要遵循Windows內存管理的一些約定,以儘量提高Windows內存的利用率。\\n\\n內存對象\\n\\nWindows應用程式可以申請分配屬於自己的內存塊,內存塊是應用程式操作內存的單位,它也稱作內存對象,在Windows中通過內存控製代碼來操作內存對象。內存對象根據分配的範圍可分為全域性內存對象和區域性內存對象;根據性質可分為固定內存對象,可移動內存對象和可刪除內存對象。\\n\\n固定內存對象,特彆是區域性固定內存對象和DOS的內存塊很類似,它一旦分配,就不會被移動或刪除,除非應用程式主動釋放它。並且對於區域性固定內存對象來說,它的內存控製代碼本身就是內存對象的16位近地址,可供應用程式直接存取,而不必象其它類型的內存對象那樣要通過鎖定在內存某固定地址後才能使用。\\n\\n可移動內存對象冇有固定的地址,Windows係統可以隨時把它們移到一個新地址。內存對象的可移動使得Windows能有效地利用自由內存。例如,如果一個可移動的內存對象分開了兩個自由內存對象,Windows可以把可移動內存對象移走,將兩個自由內存對象合併爲一個大的自由內存對象,實現內存的合併與碎片回收。\\n\\n可刪除內存對象與可移動內存對象很相似,它可以被Windows移動,並且當Windows需要大的內存空間滿足新的任務時,它可以將可刪除內存對象的長度置為0,丟棄內存對象中的數據。\\n\\n可移動內存對象和可刪除內存對象在存取前必須使用內存加鎖函數將其鎖定,鎖定了的內存對象不能被移動和刪除。因此,應用程式在使用完內存對象後要儘可能快地為內存對象解鎖。內存需要加鎖和解鎖增加了程式員的負擔,但是它卻極大地改善了Windows內存利用的效率,因此Windows鼓勵使用可移動和可刪除的內存對象,並且要求應用程式在非必要時不要使用固定內存對象。\\n\\n不同類型的對象在它所處的內存堆中的位置是不一樣的,圖6.2說明內存對象在堆中的位置:固定對象位於堆的底部;可移動對象位於固定對象之上;可刪除對象從堆的頂部開始分配。\\n\\n區域性內存\\n\\n區域性內存對象在區域性堆中分配,區域性堆是應用程式獨享的自由內存,它隻能由應用程式的特定實例訪問。區域性堆建立在應用程式的數據段中,因此,用戶可分配的區域性內存對象的最大內存空間不能超過64K。區域性堆由Windows應用程式在模塊定義檔案中用HEAPSIZE語句申請,HEAPSIZE指定以字節為單位的區域性堆初始空間尺寸。Windows提供了一係列函數來操作區域性內存對象。\\n\\n分配區域性內存對象\\n\\nLocalAlloc函數用來分配區域性內存,它在應用程式區域性堆中分配一個內存塊,並返回內存塊的控製代碼。LocalAlloc函數可以指定內存對象的大小和特性,其中主要特性有固定的(LMEM_FIXED),可移動的(LMEM_MOVEABLE)和可刪除的(LMEM_DISCARDABLE)。如果區域性堆中無法分配申請的內存,則LocalAlloc函數返回NULL。下麵的代碼用來分配一個固定內存對象,因為區域性固定內存對象的對象控製代碼其本身就是16位內存近地址,因此它可以被應用程式直接存取。\\n\\n加鎖與解鎖\\n\\n上麵程式段分配的固定區域性內存對象可以由應用程式直接存取,但是,Windows並不鼓勵使用固定內存對象。因此,在使用可移動和可刪除內存對象時,就要經常用到對內存對象的加鎖與解鎖。\\n\\n不管是可移動對象還是可刪除對象,在它分配後其內存控製代碼是不變的,它是內存對象的恒定引用。但是,應用程式無法通過內存控製代碼直接存取內存對象,應用程式要存取內存對象還必須獲得它的近地址,這通過調用LocalLock函數實現。LocalLock函數將區域性內存對象暫時固定在區域性堆的某一位置,並返回該地址的近地址值,此地址可供應用程式存取內存對象使用,它在應用程式調用LocalUnlock函數解鎖此內存對象之前有效。\\n\\n應用程式在使用完內存對象後,要儘可能早地為它解鎖,這是因為Windows無法移動被鎖住了的內存對象。當應用程式要分配其它內存時,Windows不能利用被鎖住對象的區域,隻能在它周圍尋找,這會降低Windows內存管理的效率。\\n\\n改變區域性內存對象\\n\\n區域性內存對象分配之後,還可以調用LocalReAlloc函數進行修改。LocalReAlloc函數可以改變區域性內存對象的大小而不破壞其內容:如果比原來的空間小,則Windows將對象截斷;如果比原來大,則Windows將增加區域填0(使用LMEM_ZEROINIT選項),或者不定義該區域內容。另外,LocalReAlloc函數還可以改變對象的屬性,如將屬性從LMEM_MOVEABLE改為LMEM_DISCARDABLE,或反過來,此時必須同時指定LMEM_MODIFY選項。但是,LocalReAlloc函數不能同時改變內存對象的大小和屬性,也不能改變具有LMEM_FIXED屬性的內存對象和把其它屬性的內存對象改為LMEM_FIXED屬性。\\n\\n釋放與刪除\\n\\n分配了的區域性內存對象可以使用LocalDiscard和LocalFree函數來刪除和釋放,刪除和釋放隻有在內存對象未鎖住時纔有效。\\n\\nLocalFree函數用來釋放區域性內存對象,當一個區域性內存對象被釋放時,其內容從區域性堆移走,並且其控製代碼也從有效的區域性內存表中移走,原來的內存控製代碼變為不可用。LocalDiscard函數用來刪除區域性內存對象,它隻移走對象的內容,而保持其控製代碼有效,用戶在需要時,還可以使用此內存控製代碼用LocalReAlloc函數重新分配一塊內存。\\n\\n另外,Windows還提供了函數LocalSize用於檢測對象所占空間;函數LocalFlags用於檢測內存對象是否可刪除,是否已刪除,及其鎖計數值;函數LocalCompact用於確定區域性堆的可用內存。\\n\\n全域性內存\\n\\n全域性內存對象在全域性堆中分配,全域性堆包括所有的係統內存。一般來說,應用程式在全域性堆中進行大型內存分配(約大於1KB),在全域性堆還可以分配大於64K的巨型內存,這將在後麵介紹。\\n\\n分配全域性內存對象\\n\\n全域性內存對象使用GlobalAlloc函數分配,它和使用LocalAlloc分配區域性內存對象很相似。使用GlobalAlloc的例子我們將和GlobalLock一起給出。\\n\\n加鎖與解鎖\\n\\n全域性內存對象使用GlobalLock函數加鎖,所有全域性內存對象在存取前都必須加鎖。GlobalLock將對象鎖定在內存固定位置,並返回一個遠指針,此指針在調用GlobalUnlock之前保持有效。\\n\\nGlobalLock和LocalLock稍有不同,因為全域性內存對象可能被多個任務使用,因此在使用GlobalLock加鎖某全域性內存對象時,對象可能已被鎖住,為了處理這種情況,Windows增加了一個鎖計數器。當使用GlobalLock加鎖全域性內存對象時,鎖計數器加1;使用GlobalUnlock解鎖對象時,鎖計數器減1,隻有當鎖計數器為0時,Windows才真正解鎖此對象。\\n\\n修改全域性內存對象\\n\\n修改全域性內存對象使用GlobalReAlloc函數,它和LocalReAlloc函數很類似,這裡不再贅述。修改全域性內存對象的特殊之處在於巨型對象的修改上,這一點我們將在後麵講述。\\n\\n內存釋放及其它操作\\n\\n全域性內存對象使用GlobalFree函數和GlobalDiscard來釋放與刪除,其作用與LocalFree和LocalDiscard類似。GlobalSize函數可以檢測內存對象大小;GlobalFlags函數用來檢索對象是否可刪除,是否已刪除等資訊;GlobalCompact函數可以檢測全域性堆可用內存大小。\\n\\n巨型內存對象\\n\\n如果全域性內存對象的大小為64KB或更大,那它就是一個巨型內存對象,使用GlobalLock函數加鎖巨型內存對象將返回一個巨型指針。\\n\\n巨型內存對象的修改有一點特殊性,當對象大小增加並超過64K的倍數時,Windows可能要為重新分配的內存對象返回一個新的全域性控製代碼,\\n\\n段介紹\\n\\nWindows采用段的概念來管理應用程式的內存,段有代碼段和數據段兩種,一個應用程式可有多個代碼段和數據段。代碼段和數據段的數量決定了應用程式的內存模式,圖6.2說明瞭內存模式與應用程式代碼段和數據段的關係。\\n\\n段的管理和全域性內存對象的管理很類似,段可以是固定的,可移動的和可刪除的,其屬性在應用程式的模塊定義檔案中指定。段在全域性內存中分配空間,Windows鼓勵使用可移動的代碼段和數據段,這樣可以提高其內存利用效率。使用可刪除的代碼段可以進一步減小應用程式對內存的影響,如果代碼段是可刪除的,在必要時Windows將其刪除以滿足對全域性內存的請求。被刪除的段由Windows監控,當應用程式利用該代碼段時,Windows自動地將它們重新裝入。\\n\\n代碼段\\n\\n代碼段是不超過64K字節的機器指令,它代表全部或部分應用程式指令。代碼段中的數據是隻讀的,對代碼段執行寫操作將引起通用保護(GP)錯誤。\\n\\n每個應用程式都至少有一個代碼段,例如我們前麵幾章的例子都隻有一個代碼段。用戶也可以生成有多個代碼段的應用。實際上,多數Windows應用程式都有多個代碼段。通過使用多代碼段,用戶可以把任何給定代碼段的大小減少到完成某些任務所必須的幾條指令。這樣,可通過使某些段可刪除,來優化應用程式對內存的使用。\\n\\n中模式和大模式的應用程式都使用多代碼段,這些應用程式的每一個段都有一個或幾個原始檔。對於多個原始檔,將它們分開各自編譯,為編譯過的代碼所屬的每個段命名,然後連接。段的屬性在模塊定義檔案中定義,Windows使用SEGMENTS語句來完成此任務,如下麵的代碼定義了四個段的屬性:\\n\\n用戶也可以在模塊定義檔案中用CODE語句為所有未顯式定義過的代碼段定義預設屬性。例如,要將未列在SEGMENTS語句中的所有段定義為可刪除的,可用下麵的語句:\\n\\nCODEMOVEABLEDISCARDABLE。\\n\\n數據段\\n\\n每個應用程式都有一個數據段,數據段包含應用程式的堆棧、區域性堆、靜態數據和全域性數據。一個數據段的長度也不能超過64K。數據段可以是固定的或可移動的,但不能是可刪除的。如果數據段是可移動的,Windows在將控製轉嚮應用程式前自動為其加鎖,當應用程式分配全域性內存,或試圖在區域性堆中分配超過當前可分的內存時,可移動數據段可能被移動,因此在數據段中不要保留指向變量的長指針,當數據段移動時,此長指針將失效。\\n\\n在模塊定義檔案中用DATA語句定義數據段的屬性,屬性的預設值為MOVEABLE和MULTIPLE。MULTIPLE屬性使Windows為應用程式的每一個實例拷貝一個應用程式數據段,這就是說每個應用程式實例中數據段的內容都是不同的。\\n\\n內存管理程式示例Memory\\n\\n應用程式Memory示例了部分內存管理,它是一個使用了可刪除代碼段的中模式Windows應用程式。Memory程式有四個C語言源程式,在模塊定義檔案中顯示定義了四個代碼段,相應地模塊定義檔案和makefile檔案有地些修改,讀者可通過比較Memory程式和5.1.2節的例子來體會它們之間的不同。另外,讀者在編譯和連接應用程式Memory後,可用VisualC 提供的WindowsHeapWalker(HEAPWALK.EXE)來觀察Memory運行時的各個段。\\n\\n動態連接庫\\n\\n使用動態連接庫是Windows的一個很重要的特點,它使得多個Windows應用程式可以共享函數代碼、數據和硬體,這可以大大提高Windows內存的利用率。\\n\\n動態連接庫是一個可執行模塊,它包含的函數可以由Windows應用程式調用執行,為應用程式提供服務。它和我們以前用的C函數庫相比,在功能上是很類似的,其主要區彆是動態連接庫在運行是連接,C函數庫(靜態連接庫)是在生成可執行檔案時由連接器(LINK)連接。靜態連接庫中的代碼在應用程式生成以後已經連接到應用程式模塊之中,但動態連接庫中的代碼隻有在應用程式要用到該代碼段時才動態調入DLL中的相應代碼。為了讓應用程式在執行時能夠調入DLL中正確的代碼,Windows提供了動態連接庫的引入庫。Windows在連接生成應用程式時,如果使用動態連接庫函數,連接器並不拷貝DLL中的任何代碼,它隻是將引入庫中指定所需函數在DLL中位置的資訊拷貝在應用程式模塊中,當應用程式運行時,這些定位資訊在可執行應用程式和動態連接庫之間建立動態連接。靜態庫、引入庫和動態庫之間的區彆如表6.1所示。\\n\\nDLL不能獨立執行,也不能使用訊息循環。每個DLL都有一個入口點和一個出口點,具有自己的實例控製代碼、數據段和區域性堆,但DLL冇有堆棧,它使用調用程式的堆棧。DLL也包括有.C檔案,.H檔案,.RC檔案和.DEF檔案,另外,在連接時一般要加入SDK庫中的LIBENTRY.OBJ檔案。\\n\\n創建動態連接庫\\n\\n要創建動態連接庫,至少有三個檔案:\\n\\nC語言原始檔;\\n\\n一個模塊定義檔案(.DEF);\\n\\nmakefile檔案。\\n\\n有了這些檔案後,就可以運行Microsoft的程式維護機製(NMAKE),編譯並連接源代碼檔案,生成DLL檔案。\\n\\n創建C語言原始檔\\n\\n和其它C應用程式一樣,動態連接庫可包含多個函數,每個函數要在被其它應用程式或庫使用之前用FAR聲明,並且在庫的模塊定義檔案中用EXPORTS語句引出。\\n\\n在上麵的源代碼中,有兩個函數是DLL源代碼所必需的,這就是DLL入口函數LibMain和出口函數WEP。\\n\\nLibMain函數是DLL的入口點,它由DLL自動初始化函數LibEntry調用,主要用來完成一些初始化任務。LibMain有四個參數:hint,wDataSeg,cbHeapSize和lpszCmdLine。其中hInst是動態連接庫的實例控製代碼;wDataSeg是數據段(DS)暫存器的值;cbHeapSize是模塊定義檔案定義的堆的尺寸,LibEntry函數用該值來初始化區域性堆;lpszCmdLine包含命令列的資訊。\\n\\nWEP函數是DLL的標準出口函數,它在DLL被卸出之前由Windows調用執行,以完成一些必要的清除工作。WEP函數隻使用一個參數nParameter,它用來指示終止狀態。\\n\\n原始檔中的其它函數則是DLL為應用程式提供的庫函數,DLL設計者可以給它加入自己所需要的功能,如DrawBox,DrawPie和DrawCircle。\\n\\n建立DLL模塊定義檔案\\n\\n每個DLL必須有一個模塊定義檔案,該檔案在使用LINK連接時用於提供定義庫屬性的引入資訊。\\n\\n關鍵字LIBRARY用來標識這個模塊是一個動態連接庫,其後是庫名DRAWDLL,它必須和動態連接庫檔名相同。\\n\\nDATA語句中關鍵字SINGLE是必須的,它表明無論應用程式訪問DLL多少次,DLL均隻有單個數據段。\\n\\n其它關鍵字的用法同Windows應用程式的模塊定義檔案一樣,這在前麵已有敘述,請參見5.1.2.3。\\n\\n編製Makefile檔案\\n\\nNMAKE是Microsoft的程式維護機製,它控製執行檔案的創建工作,以保證隻有必要的操作被執行。有五種工具用來創建動態連接庫:\\n\\nCL\\n\\nMicrosoftC優化編譯器,它將C語言原始檔編譯成目標檔案.OBJ。\\n\\nLINK\\n\\nMicrosoft分段可執行連接器,它將目標檔案和靜態庫連接生成動態連接庫。LINK命令列有五個參數,用逗號分開:第一個參數列出所有動態連接庫用到的目標檔案(.OBJ),如果使用了標準動態連接初始化函數,則必須包括LIBENTRY.OBJ檔案;第二個參數指示最終可執行檔名,一般用.DLL作為擴展名;第三個參數列出創建動態連接庫所需要的引入庫和靜態庫;第五個參數是模塊定義檔案。\\n\\nIMPLIB\\n\\nMicrosoft引入庫管理器,它根據動態連接庫的模塊定義檔案創建一個擴展名為.LIB的引入庫。\\n\\nRC\\n\\nMicrosoftWindows資源編譯器。所有動態連接庫都必須用RC編譯,以使它們與Windows3.1版相容。\\n\\nMAPSYM\\n\\nMicrosoft符號檔案生成器,它是可選工具,隻用於調試版本。\\n\\n程式訪問\\n\\n應用程式要訪問動態連接庫函數,它應該做下麵三件事:建立庫函數原型,調用庫函數,引入庫函數。建立庫函數原型一般通過在C語言原始檔中包含動態連接庫的頭檔案解決,\\n\\n頭檔案中包含了每個庫函數的原型語句,原型語句的目的是為編譯器定義函數的參數和返回值,以使編譯器能正確創建調用庫函數的代碼。原型語句定義好之後,應用程式就可以象調用靜態連接庫函數一樣調用動態連接庫的函數了。\\n\\n應用程式調用DLL中的引出函數還要在應用程式中對其進行引入,一般有三種方法:\\n\\n連接時隱式引入\\n\\n最常用也最簡單的方法是連接時隱式引入,這種方法是在應用程式的連接命令列中列出為動態連接庫創建的引入庫,這樣應用程式在使用DLL的引出函數時,就如同使用靜態庫中的函數一樣了。\\n\\n連接時顯式引入\\n\\n和隱式引入一樣,顯式引入也是在連接時進行的,它通過把所需函數列在應用程式的模塊定義檔案的IMPORTS語句中完成。對於在模塊定義檔案中定義了入口序號的DLL函數,采用引入函數名、動態連接庫名和入口序號的形式,如:\\n\\nIMPORTS\\n\\nDrawBox=DllDraw.2\\n\\n如果DLL的模塊定義檔案冇有定義引出函數的入口序號,則使用如下引入語句:\\n\\nIMPORTS\\n\\nDllDraw.DrawBox\\n\\n運行時動態引入\\n\\n應用程式可以在運行時動態連接DLL函數,當需要調用DLL的引出函數時,應用程式首先裝入庫,並直接檢索所需函數地址,然後才調用該函數。\\n\\n\"

}

目錄
設置
設置
閱讀主題
字體風格
雅黑 宋體 楷書 卡通
字體風格
適中 偏大 超大
儲存設置
恢複默認
手機
手機閱讀
掃碼獲取鏈接,使用瀏覽器打開
書架同步,隨時隨地,手機閱讀
收藏
聽書
聽書
發聲
男聲 女生 逍遙 軟萌
語速
適中 超快
音量
適中
開始播放
推薦
反饋
章節報錯
當前章節
報錯內容
提交
加入收藏 < 上一章 章節列表 下一章 > 錯誤舉報