精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

97视频色精品| 亚洲国产精品99| 自拍视频一区二区三区| www黄色网址| 亚洲永久网站| 日韩亚洲欧美中文在线| 亚洲自拍偷拍精品| av成人亚洲| 亚洲影视在线观看| 亚洲二区三区四区| 午夜精品无码一区二区三区| 性8sex亚洲区入口| 欧美精品日韩三级| 亚洲精品视频久久久| 天堂av中文在线| 久久久噜噜噜久久中文字幕色伊伊| 91爱视频在线| 男男做爰猛烈叫床爽爽小说| sm捆绑调教国产免费网站在线观看| 成人免费毛片嘿嘿连载视频| 久久久亚洲国产天美传媒修理工| 久久久久亚洲AV成人无码国产| av在线私库| 国产精品欧美极品| 97se国产在线视频| 黄色污污网站在线观看| 欧美成人自拍| 亚洲图片欧洲图片av| 亚洲乱妇老熟女爽到高潮的片| 俺来也官网欧美久久精品| 国产精品婷婷午夜在线观看| 国产综合色一区二区三区| 国产免费福利视频| 美国一区二区三区在线播放 | 羞羞的视频在线看| 国产精品美女久久福利网站 | 欧美日日夜夜| 日韩免费电影一区| av噜噜在线观看| 福利一区二区免费视频| 欧美日韩亚洲视频一区| 久久99中文字幕| 国产中文字幕在线播放| 99久久精品国产观看| 国产91色在线| 你懂的国产在线| 99热这里只有精品8| 久久久久国产一区二区三区| 欧美色图亚洲天堂| 91精品国产调教在线观看| 色yeye香蕉凹凸一区二区av| 亚洲黄色免费视频| 精品大片一区二区| 亚洲成人激情图| 黄色国产在线视频| 国产精品极品国产中出| 亚洲白虎美女被爆操| 美女搡bbb又爽又猛又黄www| 成人福利一区| 亚洲国产精品999| 精品夜夜澡人妻无码av| 亚洲bt欧美bt精品777| 亚洲欧美激情四射在线日| 成人免费看aa片| 成人写真视频| 久久久精品国产| 中文字幕在线看高清电影| 久9久9色综合| 中文字幕日韩欧美在线视频| 在线观看黄网址| 中文字幕一区二区三区欧美日韩| 亚洲图片在线综合| 屁屁影院国产第一页| 香蕉久久夜色精品国产更新时间 | 亚洲成av人片| 欧美成人一区二区在线观看| 桃子视频成人app| 欧美视频一区在线| www.久久com| 老牛国内精品亚洲成av人片| 亚洲男人第一网站| 亚洲图片第一页| 欧美91精品| 国产91精品高潮白浆喷水| 中文字幕高清在线免费播放| 极品av少妇一区二区| 欧美在线亚洲一区| 亚洲一区中文字幕永久在线| 日韩一区欧美二区| 欧美性在线观看| 最近中文字幕免费在线观看| 国产一区二区久久| 欧美二区三区| 欧美一区二区三区少妇| 国产精品久久久久久久岛一牛影视 | 欧美激情亚洲天堂| 亚洲校园激情春色| 欧美精品久久天天躁| 欧美美女一级片| 国产女人18毛片水真多18精品| 日韩亚洲欧美成人一区| 亚洲一区二区三区四区五区六区| 另类在线视频| 日韩少妇与小伙激情| 日韩精品一区二区在线播放| 99精品99| 亚洲一区二区三区久久| 久久伊伊香蕉| 亚洲一二三四在线| 高潮一区二区三区| 夜色77av精品影院| 欧美成人免费全部| 波多野结衣小视频| 成人免费av在线| 亚洲第一精品区| 超级碰碰久久| 亚洲成人久久一区| 裸体武打性艳史| 日本午夜一区二区| 久久av一区二区| 欧美家庭影院| 欧美蜜桃一区二区三区| 一区二区三区入口| 国产精品白丝久久av网站| 亚洲欧美在线磁力| 日本一级淫片色费放| 国产一区二区三区不卡在线观看| 国产 高清 精品 在线 a| 在线视频三区| 色播五月激情综合网| 国产黑丝一区二区| 国产日产一区| 日本高清视频一区| 天堂av在线资源| 五月婷婷久久丁香| 制服丝袜av在线| 欧美日本不卡高清| 91入口在线观看| a在线免费观看| 日韩欧美在线123| 久久免费看少妇高潮v片特黄| 日韩视频不卡| 国产亚洲一区在线播放| 牛牛精品在线| 精品国产sm最大网站免费看| 欧美成人aaa片一区国产精品| 夜久久久久久| 精品国产一区二区三区麻豆小说| 欧美日韩欧美| 欧美精品一卡两卡| 特黄一区二区三区| 一区二区福利| 久久av免费一区| 免费亚洲电影| 在线精品高清中文字幕| 做爰视频毛片视频| 国产精品情趣视频| 久久出品必属精品| 亚洲小说欧美另类婷婷| 狠狠色伊人亚洲综合网站色| 欧美激情20| 亚洲欧美激情一区| 国产精品第一页在线观看| 蜜桃一区二区三区在线| 一区二区三区|亚洲午夜| 另类一区二区| 九九热99久久久国产盗摄| 精品人妻久久久久一区二区三区| 国产精品蜜臀在线观看| 国产欧美一区二| 欧美久久九九| 蜜桃精品久久久久久久免费影院| 在线免费观看a视频| 精品乱人伦一区二区三区| 日本三级视频在线| 国产欧美精品区一区二区三区| 国内自拍在线观看| 欧美色婷婷久久99精品红桃| 国产这里只有精品| 91.xxx.高清在线| 欧美一二三区精品| 久久久午夜影院| 国产精品网站一区| 成年女人免费视频| 石原莉奈在线亚洲三区| 黄色录像特级片| 日本三级久久| 91九色单男在线观看| 九九色在线视频| 亚洲美女在线观看| 国产chinasex对白videos麻豆| 自拍偷拍亚洲综合| 疯狂揉花蒂控制高潮h| 免费在线观看不卡| 欧美又粗又长又爽做受| 精品久久网站| 国产精品日韩一区二区三区| 成人开心激情| 在线电影欧美日韩一区二区私密| 免费看日批视频| 亚洲欧美一区二区三区久本道91| 午夜激情影院在线观看| 欧美亚洲专区| 国产激情片在线观看| 经典一区二区| 国产精品青青草| 九色porny自拍视频在线观看| 亚洲精品国产精品国自产观看浪潮| 国产精品美女毛片真酒店| 日本一区二区三区四区在线视频| 中文字幕网av| 亚洲综合日韩| 欧美亚洲色图视频| 97久久视频| 日本午夜一区二区三区| 国产精品中文字幕制服诱惑| 亚洲999一在线观看www| 成人a在线观看高清电影| 97精品久久久| 羞羞的视频在线观看| 亚洲福利在线播放| 国产人妖在线播放| 欧美日韩一级片在线观看| 国产大学生自拍| 国产精品免费aⅴ片在线观看| 无码人妻久久一区二区三区蜜桃| 亚洲国产婷婷| 精品视频在线观看一区二区| 人妖一区二区三区| 成人综合av网| 亚洲国产欧美在线观看| 亚洲一区二区免费| 成人免费91| 91精品一区二区| 日韩大陆av| 国产欧美日韩91| 国产香蕉久久| 国产日韩中文字幕| 日韩欧美三区| 成人黄色av网站| 成人午夜888| 亚洲伊人第一页| 91精品国产一区二区在线观看 | 日韩欧美视频一区二区三区四区| 国产999精品在线观看| 午夜精品久久久久久久99热浪潮 | 澳门成人av| 国产成人女人毛片视频在线| 午夜久久av| 国产精品国产三级国产专区53| 成人亚洲综合| 国产精品欧美亚洲777777| 成人观看网址| 51精品在线观看| 日本性爱视频在线观看| 欧美激情亚洲激情| 思思99re6国产在线播放| 在线精品国产欧美| 日本在线丨区| 国产一区二区三区在线免费观看 | 99视频有精品高清视频| 91视频国产精品| 亚洲午夜免费| 亚洲999一在线观看www| 亚洲天堂av资源在线观看| 国产亚洲一区二区三区在线播放 | 日韩精品一二区| 亚洲免费av一区二区三区| 美女看a上一区| 色姑娘综合天天| 99精品一区二区三区| 一区二区黄色片| 国产精品久线观看视频| 麻豆一区产品精品蜜桃的特点| 国产精品福利电影一区二区三区四区 | 男人草女人视频| 在线视频精品| 亚洲综合欧美激情| 日韩av一二三| 亚洲一区二区三区三州| gogogo免费视频观看亚洲一| 欧美性猛交乱大交| 99久久精品国产毛片| 女教师淫辱の教室蜜臀av软件| 欧美激情自拍偷拍| 三级影片在线看| 亚洲成av人综合在线观看| 久久久久久国产精品视频 | 欧美日韩黄色网| 精品国产户外野外| 亚洲字幕av一区二区三区四区| 欧美乱妇15p| 五月婷婷六月激情| 久久精品欧美视频| 中文字幕成在线观看| 成人羞羞国产免费| 亚州综合一区| 精品一区二区三区毛片| 久久高清国产| 国产精品嫩草69影院| 成人免费视频app| 992在线观看| 色婷婷综合久久久中文字幕| 国产黄色高清视频| 国产亚洲免费的视频看| 97在线超碰| 95av在线视频| 美女网站色精品尤物极品姐弟| 黄色91av| 欧美在线1区| 91看片在线免费观看| 91一区二区三区在线播放| 久久久久99精品成人片试看| 欧洲在线/亚洲| 无码国产精品高潮久久99| 精品少妇v888av| 亚洲精品成人一区| 日韩精品一区二区三区四区五区| 99精品电影| 激情婷婷综合网| 精品一区二区精品| 尤物视频最新网址| 欧美日韩国产丝袜另类| 亚洲经典一区二区三区| 另类少妇人与禽zozz0性伦| 国产成人精品一区二区三区在线| 成人免费视频网| 成人女性视频| av在线无限看| 日本一区二区免费在线观看视频| 丁香花五月激情| 欧美精品亚洲二区| 91网在线播放| 国产精品久久久久久久久久三级| 国产一区二区三区黄网站 | 国产精品天天看天天狠| 四虎4hu永久免费入口| 久久国产高清| 黄色性生活一级片| 欧美日韩免费在线| 国产精品无码在线播放| 日韩第一页在线| 免费看美女视频在线网站| 国产精品美女在线观看| 国产精品一在线观看| 97在线国产视频| 免费看日韩精品| wwwww黄色| 在线不卡a资源高清| 免费a在线看| 亚洲淫片在线视频| 午夜精品久久| 人妻换人妻a片爽麻豆| 亚洲成av人片在线观看无码| 欧美 日韩 中文字幕| 97精品伊人久久久大香线蕉| 亚洲v天堂v手机在线| 日韩精品一区二区在线视频 | 亚洲黄色一级大片| 久久久亚洲国产| 夜色77av精品影院| 亚洲 欧美 日韩系列| 综合分类小说区另类春色亚洲小说欧美 | 日韩欧美在线第一页| 国产福利免费在线观看| 97精品在线视频| 亚洲人成精品久久久| 欧美婷婷精品激情| 亚洲色图在线看| 黄色成人一级片| 日韩av免费在线看| 91精品秘密在线观看| 人妖粗暴刺激videos呻吟| 色八戒一区二区三区| 黄色小网站在线观看| 风间由美一区二区三区| 老司机一区二区三区| 人妻丰满熟妇aⅴ无码| 婷婷综合另类小说色区| 国产精品99999| 国产91色在线|| 亚洲第一偷拍| 人体私拍套图hdxxxx| 欧美日韩国产首页| 人交獸av完整版在线观看| 欧美精品一区在线| 男人的天堂亚洲| 久久爱一区二区| 日韩国产一区三区| 999精品视频在线观看| 国产中文字幕二区| 国产精品视频看| 性xxxx视频播放免费| 成人美女免费网站视频| 午夜宅男久久久| 老妇女50岁三级| 中文精品99久久国产香蕉| 国产精品一区二区三区美女| 日本中文字幕网址| |精品福利一区二区三区|