精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

談一談Windows中的堆

系統 Windows
如果在Windows中編程應該了解一些Windows的內存管理,而堆(Heap)也屬于內存管理的一部分。這篇文章對你理解Windows內存分配的基本原理和調試堆內存問題或許會有所幫助。

[[413851]]

本文轉載自微信公眾號「一個程序員的修煉之路」,作者河邊一枝柳。轉載本文請聯系一個程序員的修煉之路公眾號。

如果在Windows中編程應該了解一些Windows的內存管理,而堆(Heap)也屬于內存管理的一部分。這篇文章對你理解Windows內存分配的基本原理和調試堆內存問題或許會有所幫助。

Windows Heap概述

下圖參考<<Windows高級調試>>所畫,并做了一些小小的修改。可以看出來程序中對堆的直接操作主要有三種:

  1. 進程默認堆。每個進程啟動的時候系統會創建一個默認堆。比如LocalAlloc或者GlobalAlloc也是從進程默認堆上分配內存。你也可以使用GetProcessHeap獲取進程默認堆的句柄,然后根據用這個句柄去調用HeapAlloc達到在系統默認堆上分配內存的效果。
  2. C++編程中常用的是malloc和new去申請內存,這些由CRT庫提供方法。而根據查看在VS2010之前(包含),CRT庫會使用HeapCreate去創建一個堆,供CRT庫自己使用。在VS2015以后CRT庫的實現,并不會再去創建一個單獨的堆,而使用進程默認堆。 (VS2013的CRT源碼我并未查看,有興趣的可以看看VS2013默認的CRT庫采用的是進程默認堆還是新建的堆)。
  3. 自建堆。這個泛指程序通過HeapCreate去創建的堆,然后利用HeapAlloc等API去操作堆,比如申請空間。

那么堆管理器是通過調用虛擬管理器的一些方法進行堆管理的實現,比如VirtualAlloc之類的函數。同樣應用程序也可以直接使用VirtualAlloc之類的函數對內存進行使用。

說到這里不免有些生澀,我們就寫一個示例代碼來看看一個進程的堆情況。

  1. #include <windows.h> 
  2. #include <iostream> 
  3. #include <intsafe.h> 
  4.  
  5. using namespace std; 
  6. const char* GetHeapTypeString(HANDLE pHandle) 
  7.   ULONG ulHeapInfo; 
  8.   HeapQueryInformation(pHandle, 
  9.     HeapCompatibilityInformation, 
  10.     &ulHeapInfo, 
  11.     sizeof(ulHeapInfo), 
  12.     NULL); 
  13.   switch (ulHeapInfo) 
  14.   { 
  15.   case 0: 
  16.     return "Standard"
  17.   case 1: 
  18.     return "Look Aside List"
  19.   case 2: 
  20.     return "Low Fragmentation"
  21.   } 
  22.   return "Unknow type"
  23.  
  24. void PrintAllHeaps() 
  25.  
  26.   DWORD dwNumHeap = GetProcessHeaps(0, NULL); 
  27.   if (dwNumHeap == 0) 
  28.   { 
  29.     cout << "No Heap!" << endl; 
  30.     return
  31.   } 
  32.  
  33.   PHANDLE pHeaps; 
  34.   SIZE_T  uBytes; 
  35.   HRESULT Result = SIZETMult(dwNumHeap, sizeof(*pHeaps), &uBytes); 
  36.   if (Result != S_OK) { 
  37.     return
  38.   } 
  39.  
  40.   pHeaps = (PHANDLE)malloc(uBytes); 
  41.   dwNumHeap = GetProcessHeaps(dwNumHeap, pHeaps); 
  42.   cout << "Process has heaps: " << dwNumHeap << endl; 
  43.   for (int i = 0; i < dwNumHeap; ++i) 
  44.   { 
  45.     cout << "Heap Address: " << pHeaps[i] 
  46.       << ", Heap Type: " << GetHeapTypeString(pHeaps[i]) << endl; 
  47.   } 
  48.  
  49.   return
  50.  
  51. int main() 
  52.   cout << "========================" << endl; 
  53.   PrintAllHeaps(); 
  54.   cout << "========================" << endl; 
  55.  
  56.   HANDLE hDefaultHeap = GetProcessHeap(); 
  57.   cout << "Default Heap: " << hDefaultHeap 
  58.     << ", Heap Type: " << GetHeapTypeString(hDefaultHeap) << endl; 
  59.  
  60.   return 0; 

這是一個在Win10上運行的64位程序輸出的結果: 這個進程我們并沒有在main中顯示的創建Heap,我們都知道進程在啟動的時候初始化會創建相關的資源,其中也包含了堆。這個進程共創建了四個堆。可以看出來第一個堆就是進程的默認堆,并且是采用的 Low Fragmentation的分配策略的堆。

堆的內存分配策略

堆主要有前端分配器和后端分配器,我所理解的前端分配器就是類似于緩存一樣,便于快速的查詢所需要的內存塊,當前端分配器搞不定的時候,就交給后端分配器。

前端分配器主要分為, 而Windows Vista之后進程默認堆均采用低碎片前端分配器。

  • 旁視列表 (Look Aside List)
  • 低碎片 (Low Fragmentation)

以下的場景均采用32位的程序進行的描述。

前端分配器之旁視列表

旁視列表 (Look Aside List, LAL)是一種老的前端分配器,在Windows XP中使用。

這是一個連續的數組大小為128,每個元素對應一個鏈表,因為其存儲的是整個Heap塊的大小,那就包含了用戶申請的大小+堆塊元數據,而這里元數據大小為8字節, 而最小分配粒度為8字節(32位程序),那么最小的堆塊的大小則為16個字節。從數據1~127,每個鏈表鎖存儲的堆塊大小按照8字節粒度增加。

那么當用戶申請一個比如10字節大小的的內存,則在LAL中查找的堆塊大小為18字節=10字節+元數據8字節,則在表中找到的剛好匹配的堆塊大小為24字節的節點,并將其從鏈表中刪除。

而當用戶釋放內存的時候,也會優先查看前端處理器是否處理,如果處理則將內存插入到相應的鏈表中。

前端分配器之低碎片

先說說內存碎片我這里簡要概述下: 如下圖所示假設一段大的連續的內存被分割為若干個8字節的內存塊,然后這個時候釋放了圖中綠色部分的內存塊,那么此時總共空出了40字節的內存,但想去申請一個16字節的內存塊,卻無法申請到一個連續的16字節內存塊,從而分配內存失敗,這就是內存碎片。

所謂的低碎片前端分配器,是將LAL類似的數組中的粒度重新進行了劃分:

數據Index 堆塊遞增粒度 堆塊字節范圍
0~31 8 8~256
32~47 16 272~512
112-127 512 8704~16384
 

可以看到同樣的數組的大小,將其按照不同的粒度劃分,相比較LAL分配的大小粒度逐步增大,到了最后的112-127區間粒度已經增大到了512字節,最大支持的16384。粒度更大的分配有利于緩解內存碎片,提高內存的使用效率。Windows Vista之后進程默認堆均采用低碎片前端分配器。

后端分配器

其實講到前面這部分可能還有一些人云里霧里。那么我們的內存到底是怎么劃分出來的呢?這就是后端分配器要做的事情了。看看后端分配器是如何管理這些內存的。

先說說堆在內存中的展現形式,一個堆主要由若干個Segment(段)組成,每個Segment都是一段連續的空間,然后用雙向鏈表串起來。而一般情況下,一開始只有一個Segment,然后在這個Segment上申請空間,叫做Heap Entry(堆塊)。但是這個Segment可能會被用完,那就新開辟一個Segment,而且一般新的Segement大小是原先的2倍,如果內存不足則不斷的將申請空間減半。這里有個要注意的就是當劃分了一個新的Segment后比如其空間為1GBytes,那么其真實的使用的物理內存肯定不會是1GBytes,因為此時內存還沒有被應用程序申請,這個時候實際上這個Segment只是Reserve了這段虛擬地址空間,而當真正應用程序申請內存的時候,才會一小部分一小部分的Commit,這個時候才會用到真正的物理存儲空間。

而應用程序申請的內存在Segment上叫做Entry(塊),他們是連續的,可以看到一個塊一般具有:

  • 前置的元數據: 這里主要存儲有當前塊的大小,前一個塊的大小,當前塊的狀態等。
  • 用戶數據區: 這段內存才是用戶申請并且使用的內存。當然這塊數據可能比你申請的內存要大一些,因為32位下面最小的分配粒度是8字節。這也是為什么有時候程序有時候溢出了幾個字符,好像也沒有導致程序異常或者崩潰的原因。
  • 后置的元數據: 這個一般用于調試所用。一般發布的時候不會占用這塊空間。

那么哪些塊是可以直接使用的呢?這就涉及到這些塊元數據中的狀態,可以表明這個塊是否被占用,如果是空閑狀態則可以使用。

后端分配器,不會傻傻的去遍歷所有的塊的狀態來決定是否可以分配吧?這個時候就用到了后端分配器的策略。

這個表有點類似于LAL, 只是注意看下這個index為0的多了一個list,從小到大排列,可變大小的從大于1016字節的小于524272字節的將在這個鏈表里面存儲。超過524272字節將直接通過VirtualAlloc之類的API直接獲取內存。

假設此時前端堆管理器需要尋找一個32字節的堆塊, 后端管理器將如何操作?

這個時候請求到了后端分配器,后端分配器假設也沒有在這個表中查找到32字節的空閑塊,那么將先查找64字節的空閑塊,如果找到,則將其從列表中移除,然后將其分割為兩個16字節的塊, 一個設置為占用狀態返回給應用程序,一個設置為空閑狀態插入響應的鏈表中。

那如果還沒有找到呢?那么這個時候堆管理器會從Segment中提交(Commit)更多的內存去使用,創建新的塊, 如果當前Segment空間也不夠了,那就創建新的Segement

有細心的同學可能說,那前端分配器和后端分配器差不多嗎,這里面有個很重要的就是,前端分配器鏈表中的塊是屬于占用狀態的, 而后端分配器鏈表中的塊是屬于空閑狀態的。

假設釋放內存,該如何操作?

首先要看前端分配器是否處理這個釋放的塊,比如加入到相應的鏈表中去,如果不處理,那么后端分配器將會查看相鄰的塊是否也是空閑的,如果是空閑狀態,將會采用塊合并成一個大的塊,并對相應的后端分配器鏈表進行操作。

當然了當你釋放的內存足夠多的時候,其實堆管理器也不會長期霸占著物理存儲器的空間,也會在適當的情況下調用Decommit操作來減少物理存儲器的使用。

Windbg查看進程中的堆

進程堆信息查看

進程堆的信息是放在PEB(進程環境塊)中,可以通過查看PEB相關的信息, 可以看到當前進程包含有3個堆,并且堆的數組地址為0x77756660

  1. 0:000> dt _PEB @$peb 
  2.    ...... 
  3.    +0x088 NumberOfHeaps    : 3 
  4.    ...... 
  5.    +0x090 ProcessHeaps     : 0x77756660  -> 0x00fa0000 Void 
  6.  
  7.    ...... 

然后我們查看對應的三個堆的地址,分別為0xfa0000, 0x14b0000和0x2e10000, 而第一個一般為進程的默認堆00fa0000。

  1. 0:006> dd 0x77756660 
  2. 77756660  00fa0000 014b0000 02e10000 00000000 
  3. 77756670  00000000 00000000 00000000 00000000 
  4. 77756680  00000000 00000000 00000000 00000000 
  5. 77756690  00000000 00000000 00000000 00000000 
  6. 777566a0  00000000 00000000 00000000 00000000 
  7. 777566b0  00000000 00000000 00000000 00000000 
  8. 777566c0  ffffffff ffffffff 00000000 00000000 
  9. 777566d0  00000000 020007d0 00000000 00000000 

其實上述步驟Windbg提供了一個方法可以直接查看概要信息了, 可以看到系統默認堆00fa0000為LFH堆,并且已經Reserve了空間為1128K, Commit的內存為552K。

  1. 0:000> !heap -s 
  2. ...... 
  3. LFH Key                   : 0x8302caa1 
  4. Termination on corruption : ENABLED 
  5.   Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast  
  6.                     (k)     (k)    (k)     (k) length      blocks cont. heap  
  7. ----------------------------------------------------------------------------- 
  8. 00fa0000 00000002    1128    552   1020    178    21     1    1      0   LFH 
  9. 014b0000 00001002      60     12     60      1     2     1    0      0       
  10. 02e10000 00001002    1188     92   1080      4     4     2    0      0   LFH 
  11. ----------------------------------------------------------------------------- 

可以通過dt _HEAP 00fa0000命令去查看進程默認堆的信息,也可以通過Windbg直接提供的命令去查看, 可以看到其分配空間的最小粒度(Granularity)為8字節。并且只有一個Segment.

  1. 0:006> !heap -a 00fa0000 
  2. Index   Address  Name      Debugging options enabled 
  3.   1:   00fa0000  
  4.     Segment at 00fa0000 to 0109f000 (00089000 bytes committed
  5.     Flags:                00000002 
  6.     ForceFlags:           00000000 
  7.     Granularity:          8 bytes 
  8.     Segment Reserve:      00100000 
  9.     Segment Commit:       00002000 
  10.     DeCommit Block Thres: 00000800 
  11.     DeCommit Total Thres: 00002000 
  12.     Total Free Size:      0000597f 
  13.     Max. Allocation Size: 7ffdefff 
  14.     Lock Variable at:     00fa0248 
  15.     Next TagIndex:        0000 
  16.     Maximum TagIndex:     0000 
  17.     Tag Entries:          00000000 
  18.     PsuedoTag Entries:    00000000 
  19.     Virtual Alloc List:   00fa009c 
  20.         03321000: 00100000 [commited 101000, unused 1000] - busy (b), tail fill 
  21.     Uncommitted ranges:   00fa008c 
  22.             01029000: 00076000  (483328 bytes) 
  23.     FreeList[ 00 ] at 00fa00c0: 00ffcf40 . 00ff3290   
  24.         00ff3288: 00208 . 00010 [100] - free 
  25.         00fb1370: 00060 . 00010 [100] - free 
  26.         00fb10a0: 00020 . 00010 [100] - free 
  27.         00fa6c40: 00088 . 00010 [100] - free 
  28.         00fa8e98: 00010 . 00010 [100] - free 
  29.         00fafa78: 000d0 . 00018 [100] - free 
  30.         00faea20: 00138 . 00018 [100] - free 
  31.         00fafc38: 00030 . 00020 [100] - free 
  32.         00ff4570: 00128 . 00028 [100] - free 
  33.         00faeeb8: 00058 . 00028 [100] - free 
  34.         00faf0c8: 00060 . 00028 [100] - free 
  35.         00fad980: 00050 . 00028 [100] - free 
  36.         00fb83f0: 00050 . 00040 [100] - free 
  37.         00faed78: 00030 . 00080 [100] - free 
  38.         00feebd8: 000e8 . 00080 [100] - free 
  39.         00faeb80: 00050 . 000d0 [100] - free 
  40.         00ff0398: 00148 . 000d8 [100] - free 
  41.         00fafed0: 000b0 . 000f0 [100] - free 
  42.         00fb8130: 00210 . 00270 [100] - free 
  43.         00fef460: 00808 . 003c8 [100] - free 
  44.         00ffcf38: 003c8 . 2c0a8 [100] - free 
  45.  
  46.     Segment00 at 00fa0000: 
  47.         Flags:           00000000 
  48.         Base:            00fa0000 
  49.         First Entry:     00fa0498 
  50.         Last Entry:      0109f000 
  51.         Total Pages:     000000ff 
  52.         Total UnCommit:  00000076 
  53.         Largest UnCommit:00000000 
  54.         UnCommitted Ranges: (1) 
  55.  
  56.     Heap entries for Segment00 in Heap 00fa0000 
  57.          address: psize . size  flags   state (requested size
  58.         00fa0000: 00000 . 00498 [101] - busy (497) 
  59.         00fa0498: 00498 . 00108 [101] - busy (100) 
  60.         00fa05a0: 00108 . 000d8 [101] - busy (d0) 
  61.  
  62.         ...... 
  63.         01029000:      00076000      - uncommitted bytes. 

查看Segment

一般來說我們通過上述的命令已經可以基本查看到Segment在一個堆中的信息了。如果要針對一個Segment進行查看可以用如下方式:

  1. 0:006> dt _HEAP_SEGMENT 00fa0000 
  2. ntdll!_HEAP_SEGMENT 
  3.    +0x000 Entry            : _HEAP_ENTRY 
  4.    +0x008 SegmentSignature : 0xffeeffee 
  5.    +0x00c SegmentFlags     : 2 
  6.    +0x010 SegmentListEntry : _LIST_ENTRY [ 0xfa00a4 - 0xfa00a4 ] 
  7.    +0x018 Heap             : 0x00fa0000 _HEAP 
  8.    +0x01c BaseAddress      : 0x00fa0000 Void 
  9.    +0x020 NumberOfPages    : 0xff 
  10.    +0x024 FirstEntry       : 0x00fa0498 _HEAP_ENTRY 
  11.    +0x028 LastValidEntry   : 0x0109f000 _HEAP_ENTRY 
  12.    +0x02c NumberOfUnCommittedPages : 0x76 
  13.    +0x030 NumberOfUnCommittedRanges : 1 
  14.    +0x034 SegmentAllocatorBackTraceIndex : 0 
  15.    +0x036 Reserved         : 0 
  16.    +0x038 UCRSegmentList   : _LIST_ENTRY [ 0x1028ff0 - 0x1028ff0 ] 

查看申請的內存地址

其實在調試過程中一般最關注的是變量的地址關聯的內容信息。比如說我寫了個程序其申請的內存變量地址為0x00fb5440, 申請的大小為5字節。

首先可以通過如下命令查找到地址所在的位置為堆:

  1. 0:000> !address 0x00fb5440 
  2.  
  3. Building memory map: 00000000 
  4. Mapping file section regions... 
  5. Mapping module regions... 
  6. Mapping PEB regions... 
  7. Mapping TEB and stack regions... 
  8. Mapping heap regions... 
  9. Mapping page heap regions... 
  10. Mapping other regions... 
  11. Mapping stack trace database regions... 
  12. Mapping activation context regions... 
  13.  
  14. Usage:                  Heap 
  15. Base Address:           00fa0000 
  16. End Address:            01029000 
  17. Region Size:            00089000 ( 548.000 kB) 
  18. State:                  00001000          MEM_COMMIT 
  19. Protect:                00000004          PAGE_READWRITE 
  20. Type:                   00020000          MEM_PRIVATE 
  21. Allocation Base:        00fa0000 
  22. Allocation Protect:     00000004          PAGE_READWRITE 
  23. More info:              heap owning the address: !heap 0xfa0000 
  24. More info:              heap segment 
  25. More info:              heap entry containing the address: !heap -x 0xfb5440 

然后可以通過如下命令查看當前申請內存的詳細堆塊信息, 其處于被占用狀態(busy)。可以看到其堆塊的大小為0x10, 我們實際申請的內存為5字節,那么0x10(Size) - 0xb (Unused) = 5, 可以看出來Unused是包含了_HEAP_ENTRY塊元數據的大小的。而我們實際用戶可用的內存是8字節 (最小分配粒度),比我們申請的5字節多了三個字節,這也是為什么程序有時候溢出了幾個字符,并沒有導致程序崩潰或者異常的原因。

  1. 0:000> !heap -x 0xfb5440 
  2. Entry     User      Heap      Segment       Size  PrevSize  Unused    Flags 
  3. ----------------------------------------------------------------------------- 
  4. 00fb5438  00fb5440  00fa0000  00fad348        10      -            b  LFH;busy 

那么我們也可以直接查看Entry的結構:

  1. 0:000> dt _HEAP_ENTRY 00fb5438 
  2. ntdll!_HEAP_ENTRY 
  3.    +0x000 UnpackedEntry    : _HEAP_UNPACKED_ENTRY 
  4.    +0x000 Size             : 0xa026 
  5.    +0x002 Flags            : 0xdc '' 
  6.    +0x003 SmallTagIndex    : 0x83 '' 
  7.    +0x000 SubSegmentCode   : 0x83dca026 
  8.    +0x004 PreviousSize     : 0x1b00 
  9.    +0x006 SegmentOffset    : 0 '' 
  10.    +0x006 LFHFlags         : 0 '' 
  11.    +0x007 UnusedBytes      : 0x8b '' 
  12.    +0x000 ExtendedEntry    : _HEAP_EXTENDED_ENTRY 
  13.    +0x000 FunctionIndex    : 0xa026 
  14.    +0x002 ContextValue     : 0x83dc 
  15.    +0x000 InterceptorValue : 0x83dca026 
  16.    +0x004 UnusedBytesLength : 0x1b00 
  17.    +0x006 EntryOffset      : 0 '' 
  18.    +0x007 ExtendedBlockSignature : 0x8b '' 
  19.    +0x000 Code1            : 0x83dca026 
  20.    +0x004 Code2            : 0x1b00 
  21.    +0x006 Code3            : 0 '' 
  22.    +0x007 Code4            : 0x8b '' 
  23.    +0x004 Code234          : 0x8b001b00 
  24.    +0x000 AgregateCode     : 0x8b001b00`83dca026 

如果細心的同學可以能會發現以下兩個問題:

  1. 結構中Size的值是0xa026和之前命令中看到的大小0x10不一樣,這個是因為Windows對這些元數據做了編碼,需要用堆中的一個編碼數據做異或操作才能得到真實的值。具體方法筆者試過,在這里不在贅述,可以在參考文章中獲取方法。
  2. Size是2字節描述,那么最大可以描述的大小應該為0xffff,但是之前不是說最大的塊可以是0x7FFF0 (524272字節), 應該不夠存儲啊?這個也和第一個問題有關聯,在通過上述方法計算出的Size之后還需要乘以8, 才是真正的數據大小。

Windows 自建堆的使用建議

在<

保護組件

先看看書中原話:

假如你的應用程序需要保護兩個組件,一個是節點結構的鏈接表,一個是 B R A N C H結構的二進制樹。你有兩個源代碼文件,一個是 L n k L s t . c p p,它包含負責處理N O D E鏈接表的各個函數,另一個文件是 B i n Tr e e . c p p,它包含負責處理分支的二進制樹的各個函數。

現在假設鏈接表代碼中有一個錯誤,它使節點 1后面的8個字節不

小心被改寫了,從而導致分支 3中的數據被破壞。當B i n Tr e e . c p p文件中的代碼后來試圖遍歷二進制樹時,它將無法進行這項操作,因為它的內存已經被破壞。當然,這使你認為二進制樹代碼中存在一個錯誤,而實際上錯誤是在鏈接表代碼中。由于不同類型的對象混合放在單個堆棧中,因此跟蹤和確定錯誤將變得非常困難。

我個人認為在一個應用的工程中,也許不需要做到上述那么精細的劃分。但是你想一想,在一個大型工程中,會混合多個模塊。比如你是做產品的,那么產品會集成其他部門甚至是外部第三方的組件,那么這些組件同時在同一個進程,使用同一個堆的時候,那么難免會出現,A模塊的內存溢出問題,導致了B模塊的數據處理異常,從而讓你追蹤問題異常復雜,更坑的是,很可能讓B模塊的團隊背鍋了。而這些是切實存在的。 這里的建議更適合于讓一些關鍵模塊使用自己的堆,從而降低自己內存使用不當,覆蓋了其他組件使用的內存,從而導致異常,讓問題的追蹤可以集中在出錯的模塊中。當然這也不是絕對的,因為進程的組件都在同一個地址空間內,內存破壞也存在一種跳躍式內存訪問破壞,但是大多數時候內存溢出是連續的上溢較多,這樣做確實可以提高這種問題追蹤的效率。

更有效的內存管理

這個主要強調是,將同種類型大小的對象放在一個堆中,盡量避免不同大小內存對象摻雜在一起導致的內存碎片問題,從而帶來的堆管理效率下降。同一種對象,則可以避免內存碎片問題。當然了這些只是提供了一種思想,至于你的工程是否有必要采用這樣的做法,由工程師自己來做決定。

進行本地訪問

先來看看原文的描述:

每當系統必須在 R A M與系統的頁文件之間進行 R A M頁面的交換時,系統的運行性能就會受到很大的影響。如果經常訪問局限于一個小范圍地址的內存,那么系統就不太可能需要在 R A M與磁盤之間進行頁面的交換。

所以,在設計應用程序的時候,如果有些數據將被同時訪問,那么最好把它們分配在互相靠近的位置上。讓我們回到鏈接表和二進制樹的例子上來,遍歷鏈接表與遍歷二進制樹之間并無什么關系。如果將所有的節點放在一起(放在一個堆棧中),就可以使這些節點位于相鄰的頁面上。實際上,若干個節點很可能恰好放入單個物理內存頁面上。遍歷鏈接表將不需要 C P U為了訪問每個節點而引用若干不同的內存頁面。

這個思想其實就是一種Cache思想,RAM與磁盤上的page.sys存儲器(磁盤上的虛擬內存)進行頁交換會帶來一些時間成本。舉個極限的例子,你的RAM只有一個頁,你有兩個對象A和B,A存放在Page1上,而B存放在Page2上,當你訪問A對象的時候,必然要把Page1的內容加載到RAM中,那么這個時候B對象所在Page2肯定就在page.sys中,當你又訪問B對象的時候,這個時候就得把Page2從page.sys中加載到RAM中替換掉Page1.

理解了頁切換帶來的性能開銷后,其實這一段的思想就是將最可能連續訪問的對象放在一個堆中,那么他們在一個頁面的可能性也更大,提高了效率。

減少線程同步的開銷

這一個很好理解,一般情況下創建的自建堆是支持多線程的,那么多線程的內存分配必然會帶來同步的時間消耗,但是對于有些工程來說,只有一個線程,那么對于這一個線程的程序,在調用HeapCreate的時候設置HEAP_NO_SERIALIZE, 則這個堆只支持單線程,從而提高內存申請的效率。

迅速釋放堆棧

這種思想第一提高了內存釋放的效率,第二是盡可能的降低了內存泄露。記得之前看過一篇文章介紹過Arena感覺比較類似,在一個生命周期內的內存是從Arena申請,然后這個聲明周期結束后,不是直接釋放各個對象,而是直接銷毀這個Arena,提高了釋放效率,并且降低了內存泄露的可能。那么使用自建堆的原理和Arena是類似的,比如在一個任務處理之前創建一個堆,在任務處理過程中所申請的內存在這個堆上申請,然后釋放的時候,直接銷毀這個堆即可。

那對于對象的申請,C++中可以重載new和delete等操作符,來實現自定義的內存分配,并且可以將這個先封裝成一個基類,在這個過程中需要創建的對象均繼承于這個基類,復用new和delete。

總結和參考

我本以為這些是已經掌握的知識,但是寫文章的時間也超過了我預想的時間,在實踐中也也發現了一些自己曾經錯誤的理解。如果文中還有不當的地方,也希望讀者給與指正。

參考

《Windows核心編程》

《Windows高級調試》

Windows Heap Chunk Header Parsing and Size Calculation: https://stackoverflow.com/questions/28483473/windows-heap-chunk-header-parsing-and-size-calculation

Understanding the Low Fragmentation Heap: http://www.illmatics.com/Understanding_the_LFH.pdf

 

WINDOWS 10SEGMENT HEAP INTERNALS: https://www.blackhat.com/docs/us-16/materials/us-16-Yason-Windows-10-Segment-Heap-Internals-wp.pdf

 

責任編輯:武曉燕 來源: 一個程序員的修煉之路
相關推薦

2022-07-04 10:51:27

數據中臺數據倉庫

2021-02-19 09:19:11

消息隊列場景

2018-08-21 14:42:29

閃存存在問題

2022-02-14 22:22:30

單元測試Junit5

2014-07-17 10:11:53

Android LAPI谷歌

2018-01-11 09:51:34

2021-05-11 08:48:23

React Hooks前端

2021-11-23 09:45:26

架構系統技術

2017-11-21 14:32:05

容器持久存儲

2015-03-27 15:07:55

云計算IaaS平臺Docker

2016-07-08 13:33:12

云計算

2016-10-09 23:47:04

2021-03-15 22:42:25

NameNodeDataNode分布式

2011-07-28 09:22:56

Oracle WDPOracle數據庫

2020-04-08 10:18:56

MySQL數據庫SQL

2019-01-30 10:59:48

IPv6Happy EyebaIPv4

2018-08-28 06:42:06

邊緣計算SDNMEC

2020-06-19 15:32:56

HashMap面試代碼

2019-11-12 08:40:03

RocketMQ架構

2018-09-05 15:15:58

來電顯示來電顯示欺詐身份
點贊
收藏

51CTO技術棧公眾號

伊人色综合影院| 国产精选久久久久久| 亚洲少妇一区二区三区| 天堂电影一区| 国产日韩欧美精品一区| 91麻豆桃色免费看| 粉嫩aⅴ一区二区三区| 日韩成人精品一区二区| 日韩精品一区二区三区四区| av免费播放网址| 看女生喷水的网站在线观看| av在线播放成人| 国产欧美精品xxxx另类| 91浏览器在线观看| 久久久久久美女精品| 精品偷拍一区二区三区在线看| 亚洲欧美视频二区| 亚洲黄色网址| 一区二区三区免费网站| 天天人人精品| 噜噜噜久久,亚洲精品国产品| 青青草视频一区| 51色欧美片视频在线观看| 日韩精品一区二区亚洲av性色 | 日本不卡在线视频| 91精品国产精品| 午夜免费激情视频| 日本久久精品| 亚洲免费一级电影| 中文字幕一区三区久久女搜查官| 96sao精品免费视频观看| 色菇凉天天综合网| 国产乱子伦农村叉叉叉| 日本在线视频www鲁啊鲁| 国产精品国产三级国产普通话三级| 久久久久无码国产精品一区| 丰满人妻一区二区三区四区53| 久久精品国产精品青草| 国产精品99导航| 国产www在线| 在线综合亚洲| 97高清免费视频| 国产真人真事毛片| 亚洲视频免费| 欧美激情va永久在线播放| 国产福利在线导航| 郴州新闻综合频道在线直播| 亚洲欧美日韩区| 在线观看福利片| 伊人成综合网yiren22| 亚洲精品成人久久| 人妻丰满熟妇aⅴ无码| 精品福利一区| 精品无码久久久久久国产| 亚洲制服丝袜在线播放| 婷婷综合福利| 亚洲欧洲xxxx| 最新中文字幕av| 欧美日韩水蜜桃| 中文字幕亚洲一区在线观看| 手机免费观看av| 成人三级视频| 久久久91精品国产一区不卡| 久久久久久久久久网站| 亚洲午夜激情在线| 2019av中文字幕| 亚洲欧美一二三区| 久久99精品久久久久久动态图| 成人黄色大片在线免费观看| 国产黄色美女视频| 不卡视频一二三| 欧美视频1区| 91视频在线观看| 亚洲情趣在线观看| 免费无码毛片一区二三区| 亚洲欧美电影| 欧美精品在线观看一区二区| 日本亚洲一区二区三区| 日韩深夜影院| 色哟哟入口国产精品| 丝袜 亚洲 另类 欧美 重口 | 欧美激情视频在线| 六月丁香激情综合| 精品一二三四在线| 精选一区二区三区四区五区| sese一区| 亚洲高清中文字幕| 国产小视频精品| 在线日韩成人| 亚洲午夜精品视频| 超碰手机在线观看| 久久久xxx| 91探花福利精品国产自产在线 | 99精品欧美一区二区三区综合在线| 欧美下载看逼逼| av网站免费在线观看| 日韩欧美高清在线视频| 想看黄色一级片| 偷拍亚洲精品| 精品久久国产精品| 波多野结衣视频网站| 国产在线国偷精品产拍免费yy| 精品日产一区2区三区黄免费 | 丰满人妻一区二区| 国产精品久久久一本精品 | www欧美xxxx| 欧美性videosxxxxx| a天堂视频在线观看| 香蕉综合视频| 国产精品激情自拍| 日韩在线视频第一页| 国产精品国产三级国产aⅴ无密码 国产精品国产三级国产aⅴ原创 | 97人人爽人人喊人人模波多| 91亚洲国产成人精品一区| 91蝌蚪porny成人天涯| 无颜之月在线看| 国内精品伊人| 亚洲精品天天看| 免费在线观看av网址| 久久国产精品色| 欧美日韩精品一区| 国产va在线视频| 日韩欧美国产午夜精品| 北条麻妃在线观看视频| 日韩电影在线免费观看| 久久久久久国产精品mv| 福利在线导航136| 欧美一区二区三区视频在线| 亚洲图片第一页| 日韩中文字幕1| 欧美二区三区在线| 欧美xxxhd| 精品国产乱码久久久久久夜甘婷婷 | 国产欧美视频在线观看| 人妻无码视频一区二区三区| 天堂av一区二区三区在线播放| 久久久人成影片一区二区三区| 国产三级在线观看视频| 自拍偷拍亚洲欧美日韩| 夜夜夜夜夜夜操| 小小影院久久| 成人羞羞国产免费| 成人在线观看亚洲| 7777精品伊人久久久大香线蕉超级流畅 | 日本三级一区二区三区| 久久久高清一区二区三区| 91看片就是不一样| 精品国产一区二区三区香蕉沈先生 | 色成人免费网站| 国产午夜精品视频| 最近中文字幕免费在线观看| 亚洲国产成人自拍| 美女在线视频一区二区| 久久久久久久久99精品大| 91九色视频在线观看| 日韩免费影院| 精品国产91亚洲一区二区三区婷婷| 欧美又粗又大又长| 成人av电影在线观看| 黄色网页免费在线观看| 网红女主播少妇精品视频| 国产成人精品久久| 午夜免费视频在线国产| 制服丝袜日韩国产| 精品少妇久久久| 91麻豆蜜桃一区二区三区| 日韩 欧美 高清| 日韩欧美视频专区| 91久久极品少妇xxxxⅹ软件| 77thz桃花论族在线观看| 日韩成人av在线播放| 精品久久久久久久久久久国产字幕 | 欧美另类变人与禽xxxxx| 91插插插插插插| 成人福利在线看| 可以在线看的黄色网址| 91麻豆精品国产91久久久平台 | jlzzjlzzjlzz亚洲人| 五月综合激情网| 精品无码人妻一区二区免费蜜桃| 精品亚洲porn| 欧美在线一区视频| 欧美亚洲国产精品久久| 91免费在线观看网站| 亚洲天堂电影| 久久久999精品视频| 色窝窝无码一区二区三区| 欧美综合一区二区三区| 免费在线观看av网址| 国产女同互慰高潮91漫画| 成人三级做爰av| 鲁大师影院一区二区三区| www.-级毛片线天内射视视| 欧美激情影院| 91香蕉亚洲精品| 亚洲精品福利电影| 欧美激情国产日韩精品一区18| 黄色av网址在线免费观看| 日韩欧美一级在线播放| 国产美女www爽爽爽| 亚洲成人动漫精品| 日韩在线不卡av| 久久久综合视频| 污污的视频免费| 免播放器亚洲| 精品无码一区二区三区爱欲| 99久久亚洲精品蜜臀| 麻豆亚洲一区| jazzjazz国产精品久久| 国产精品专区一| 中文字幕在线看片| 久久欧美在线电影| 18av在线播放| 色婷婷成人综合| 黄色在线观看网| 亚洲精品电影在线| 国产成人精品无码高潮| 欧美色图片你懂的| 无码人妻丰满熟妇奶水区码| 亚洲国产综合色| 成人免费精品动漫网站| 国产清纯在线一区二区www| 极品粉嫩小仙女高潮喷水久久| 国产精品一区专区| 思思久久精品视频| 麻豆国产精品777777在线| 日韩欧美在线播放视频| 亚洲全部视频| 欧美国产日韩激情| 欧美网站在线| 丰满人妻一区二区三区53号| 在线国产一区| 日本女人高潮视频| 国产精品伦理久久久久久| 亚洲一区不卡在线| 成人激情开心网| 天堂va久久久噜噜噜久久va| 国产精品一国产精品| 免费中文日韩| 日韩av午夜| 久久久久久久久一区| 激情小说一区| 久久riav| 亚洲精华一区二区三区| 精品日本一区二区三区| 中文字幕亚洲影视| 日韩精品另类天天更新| sdde在线播放一区二区| 亚洲三区四区| 66视频精品| 国内精品国产三级国产99| 欧美成人首页| 黄色激情在线视频| 国产视频亚洲| 人妻丰满熟妇av无码区app| 日日夜夜精品视频天天综合网| 亚洲精品视频导航| 精一区二区三区| 深夜做爰性大片蜜桃| 成人在线综合网| 中文字幕日韩三级片| 久久久久国产精品厨房| 精品熟妇无码av免费久久| 亚洲天天做日日做天天谢日日欢 | 99成人精品| 中文字幕日本最新乱码视频| 三级一区在线视频先锋| 欧美激情第3页| 国产寡妇亲子伦一区二区| 亚洲av成人精品一区二区三区| 91久色porny| 国产aaaaaaaaa| 亚洲永久免费av| 丁香六月婷婷综合| 91精品免费在线观看| 亚洲精品中文字幕成人片| 日韩精品久久久久久久玫瑰园 | 国产精品久久久久久妇女6080 | 亚洲小说欧美另类婷婷| 免费在线观看日韩视频| 精一区二区三区| 中文字幕在线播放视频| 国产精品国产三级国产普通话三级| 久久久久久免费观看| 色久综合一二码| 99热这里只有精品5| 国产视频自拍一区| 欧美成人高清在线| 91wwwcom在线观看| 96sao精品免费视频观看| 久久偷看各类wc女厕嘘嘘偷窃 | 日本一区二区综合亚洲| 91视频免费在线看| 91成人免费在线视频| 亚洲成人av综合| 中文字幕欧美专区| 高清毛片在线观看| 3d精品h动漫啪啪一区二区 | 97久久中文字幕| 欧美第一黄网| 国内成人在线| 精品亚洲视频在线| 久久人人97超碰com| 欧美极品aaaaabbbbb| 欧美揉bbbbb揉bbbbb| 天堂av资源网| 欧美久久精品午夜青青大伊人| 外国成人直播| 国产一区二区在线网站| 一区二区电影在线观看| 国产精品igao| 91免费版在线看| 久久免费视频6| 在线不卡免费欧美| 福利在线视频导航| 欧美在线视频在线播放完整版免费观看 | 青草视频在线免费直播| 国产精品揄拍一区二区| 国产一区二区观看| 日本精品一区在线观看| youjizz久久| 久久精品国产亚洲av香蕉| 69堂国产成人免费视频| www.成人.com| 国产精品99蜜臀久久不卡二区| 日韩欧美美女在线观看| 精品一区二区三区无码视频| 国产在线视频精品一区| 国产精品视频看看| 欧美亚日韩国产aⅴ精品中极品| 青青青手机在线视频观看| 性欧美办公室18xxxxhd| 999久久精品| 又大又硬又爽免费视频| 国产精品99久久久久久宅男| 亚洲伦理一区二区三区| 欧美日韩国产综合视频在线观看| 黄色av免费在线看| 国产成人精品综合| 精品国产美女| 污视频免费在线观看网站| 亚洲国产精品黑人久久久| 波多野结衣毛片| 一区二区三区高清国产| 欧美福利在线播放| 亚洲蜜桃av| 精品午夜久久福利影院| 麻豆精品一区二区三区视频| 欧美白人最猛性xxxxx69交| 手机在线免费看av| 国产精品久久国产三级国电话系列| 韩日欧美一区| 国产精品久久AV无码| 欧美性猛xxx| 国产黄色片在线播放| 国产三级精品网站| 亚洲人成免费网站| 成人在线观看一区二区| 婷婷久久综合九色综合伊人色| 亚洲 精品 综合 精品 自拍| 2021久久精品国产99国产精品| 免费看av成人| 国产性生活一级片| 国产精品人人爽人人做我的可爱| 日韩成人黄色av| 超免费在线视频| 蜜桃传媒一区二区| 欧美bbbbb| 欧美成人精品一区二区免费看片| 欧美成人精品1314www| 国产伦久视频在线观看| 色女人综合av| 国内国产精品久久| 亚欧洲精品在线视频| 亚洲视频在线观看网站| 自拍偷拍亚洲| 日韩a∨精品日韩在线观看| 久久久久久久久久美女| 国产原创中文av| 韩国三级电影久久久久久| 精品在线手机视频| 午夜av中文字幕| 欧美日韩国产精品一区二区不卡中文| 日韩电影免费| 91在线播放国产| 国产偷自视频区视频一区二区| 怡红院一区二区三区| 日韩欧美成人午夜| 日韩影片中文字幕| 日韩一二区视频| 国产三级三级三级精品8ⅰ区| 99riav国产| 国产不卡精品视男人的天堂| 欧美成人首页| 阿v天堂2014| 亚洲黄色av女优在线观看| 日韩午夜视频在线| 国产免费成人在线| 依依成人综合视频| 91美女视频在线|