探索Windows的記憶體機制

來源:互聯網
上載者:User
記憶體是作業系統的核心部分,所以我們非常有必要瞭解記憶體的分配機制。在DOS下,訪問記憶體的指標是用段地址:位移量來表示,所有程式共用一個記憶體空間,由低向高分配記憶體空間,所以任何程式都可以隨便修改記憶體中的資料,包括不屬於自己程式的記憶體空間和中斷向量表。而且所有程式被局限在1M的基本記憶體(Base Memory)中,不能直接存取擴充記憶體。對於Windows下的程式來說,它所訪問的記憶體位址不再是真實的。而是虛擬、獨立的全平坦式(flat)的記憶體空間。如一個32位的程式可訪問記憶體位址是0x00000000到0xffffffff(4G),指標不再儲存段地址。所謂獨立,指的是當進程A載入到記憶體0x400000處時,進程B載入到記憶體的地址時一樣是0x400000,兩者的地址空間是相互獨立的。程式訪問記憶體位址時,由Windows自動換算為真實的記憶體位址。這樣,程式A是無法直接存取程式B的記憶體空間的,也就提高了系統的穩定性。
    其實,在這4G的記憶體位址中,我們能使用的記憶體位址不到一半。我們的程式是無法分配到0x80000000以上的記憶體位址的,這2G的記憶體位址都是被系統佔用,是唯讀。如果一個應用程式企圖對大於0x80000000的記憶體位址進行寫操作,則會產生"非法操作"。例如: char *pstr; if(pstr!=NULL) *ptr='a'; 這裡聲明了一個指標,在沒有對其初始化時,它指向0xCCCCCCCC(這裡很奇怪不知為什麼所有沒初始化的變數,其值都為0xCCCCCCCC),所以不為空白,因而執行 *pstr=“a” 。由於0xCCCCCCCC在0x80000000以上,產生了一個“非法操作”,這就是有的朋友常犯的錯誤。
    在Win2000中,使用者可以使用0x80000000以上的1G地址(在Boot.ini 中加上參數 /3G) 不過似乎對於普通程式員來說,是沒有什麼意義的。 Win9x記憶體中與Win2000的記憶體配置略有不同。在Win98下,應用程式可使用的記憶體是從0x400000開始,而在Win2000下,是從0x100000開始。可是載入應用程式時,都是載入到0x400000 。試試這段代碼: BYTE buff[0x300]; DWORD dwRead; ReadProccessMemory(GetCurrentProcess(),(PVOID)0x400000,buff,0x300,&dwRead); 請把Buff輸出到檔案中,你會發現正好是你的應用程式的檔案頭。 
    Windows的記憶體配置是以頁(page)為單位(與磁碟的扇區很相似),每一頁的大小因CPU而異。我們常用的PC機是x86構架,每頁為4096位元組,可以用GetSystemInfo()函數得到頁面大小。一個記憶體塊由多個頁面組成。每個頁都有一個訪問屬性: PAGE_NOACCESS 不可訪問 PAGE_READONLY 唯讀 PAGE_READWRITE 讀寫 PAGE_EXECUTE 可執行 …… 我們可以用: DWORD VirtualQuery( LPCVOID lpAddress, // 記憶體位址 PMEMORY_BASIC_INFORMATION lpBuffer, // 記憶體位址資訊 DWORD dwLength // lpBuffer 的大小,也就是結構 MEMORY_BASIC_INFORMATION 的大小 ); 來得到某個記憶體塊的狀態。使用 VirtualQueryEx() 還可以訪問其它進程的記憶體配置資訊。要注意的是這兩個函數是得到一個記憶體塊的資訊,而不是一個頁的資訊。在Win9x,當對一個低於0x80000000地址進行寫操作時,即使此記憶體位址為唯讀(PAGE_READONLY)或是此記憶體根本就沒有分配,Win9x一般也不會報錯,這樣使得Win9x相容性好了些,但也使Win9x非常脆弱。在Win2000下則嚴格遵守頁面屬性規則,如果你企圖對非寫屬性的記憶體位址進行操作時,會立刻被中斷。這就是為什麼很多程式能在Win9x下運行,而轉到Win2000下就出現非法操作的原因。雖然我們不能直接讀取其它進程的記憶體空間,但Windows也還是給我們留下了一個後門: ReadProcessMemory()、WriteProcessMemory()。這兩個函數可以直接對其它進程的記憶體進行讀寫操作。也許你會發現,如果Windows同時啟動兩次一個相同的應用程式(如同時啟動兩個Visual Studio),第二次起動的速度明顯快於第一次。這是因為,雖然每個程式的記憶體空間是獨立的,但它們有一部分記憶體是唯讀,是可以幾個進程共用包括放在0x80000000記憶體空間以上的系統服務。Windows提供這個功能可以很有效節省記憶體空間,提高系統的效率。談到這裡,我們又有問題了,如何在兩個進程之間的正常的進行資料交換,而不是用ReadProcessMemory的WriteProcessMemory去強行讀寫呢? 常用的方法是用MapViewOfFile()把檔案對應到記憶體中,其它進程可以用OpenViewOfFile()來訪問,或是用Windows的DDE資料轉送協議。還有一種訪問是放到DLL中,通過: #pragma data_seg("AllUser") ....//資料 #pragma data_seg() #pragma comment(linker,"/SECTION:AllUser,SRW") // SRW 為共用讀寫 把某個記憶體塊設定為公用記憶體,那麼就可象使用全域變數一樣訪問此記憶體空間。 
    如果有這樣一個問題,下面的Struct 在記憶體中佔用多大的位元組。 struct{ char a[2]; int b; }abc; 也許你會這樣回答,在Windows下Char佔一個位元組,Int佔4個位元組,所以是6個位元組。其實如果我們用Sizeof得到的大小是8個位元組。因為在Windows下對結構變數分配記憶體空間預設大小是8的倍數。它不再象DOS一樣按本身結構的大小分配,這是由於CPU訪問對齊的資料速度要比訪問非對齊資料速度要快好幾倍。如果你的資料量太大,希望按真實大小對結構分配空間,則可以設定 Project => Setting => C/C++ => Category => Code Generation => struct member alignment 將其設為1就行了。分配非記憶體對齊的變數同樣也會影響執行速度,如: char *pstr=(char *)malloc(5); DWORD *pdw=(DWORD *)(pstr+1); PDW指標指向的就是一個奇數地址,對PDW操作時就很費時。
    小結:對於一個VC程式員來說,很多程式的錯誤都出現在記憶體配置上,如未經檢查就使用一個null 指標等,如果能很好的管理、分配你程式的記憶體空間,你的程式將會少去一些不必要的錯誤。 
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.