C ++のメモリ管理4 - Windowsのヒープマネージャープログラミング(RPM)

 

1はじめに

  ほとんどのWindowsアプリケーションでは、メモリの操作と管理にはほとんど避けられない設計。では、メモリ、大サイズの動的割り当て重要特に顕著。本論文では、主に、ヒープメモリ管理の管理技術が議論されています。

  ヒープ(ヒープ)は、実際にされている予約に位置の仮想アドレス空間領域初めに、保持領域内のページの大部分は、物理メモリに提出されていません。より多くのヒープから割り当てられたメモリとして、ヒープマネージャは徐々にヒープに提示し、より多くの物理メモリを配置します。システム内の文書のページのヒープから割り当てられた物理メモリは、専用のヒープマネージャは、リリース時に占有物理メモリの回復のために責任があります。また、経営陣は、Windowsが提供ヒープメモリ管理です。主に小さなデータ・ブロックを分配するため他の2つのWindowsのメモリ管理と仮想メモリ(1)の及び(2)のメモリマップされたファイルが比較ヒープが問題を無視するように面倒かつ簡単のような割り当ての粒度とページの境界線のシステムとして検討する必要はありません、注意が可能力は、プログラムコードの設計機能に焦点を当てました。しかし、の使用に割り当てるヒープと解放メモリの速度は、他の2つのメカニズムよりもはるかに遅いですが、直接物理メモリの制御と回復を提出することはできません

  で、時間のプロセスが始まったばかり、システムが新たに作成されたになりますプロセスの仮想アドレス空間のヒープを作成しデフォルトのサイズは1MBですデフォルトのプロセス・ヒープは、ヒープは、この値は、ときに、リンクプログラム、である(変更を許可されています接続設定- -セットすることができます)プロジェクト設定で、VSコンパイラデフォルトのプロセス・ヒープは、Windowsの機能の使用回数のために、より重要です使用時には、システムは、指定した時間内に、これが唯一のスレッドがそれぞれ割り当てと割り当て解除のデフォルトのヒープメモリブロックのことが可能であることが保証されなければなりません。が、そのような制限は、アクセス速度に影響する場合がありますが、それは同時に、さまざまなWindowsの機能を呼び出す処理にデフォルトのスタック複数のスレッドへのシーケンシャルアクセスを確保することができます

  これは、以下を含む、プロセスのデフォルトヒープを含むプロセスで複数のスタック、使用可能に各スタックを識別するために、ヒープハンドルを持っていますそして、完全にシステムが破壊され、デフォルトヒープで作成された異なるプロセス、独自のスタック、作成およびすでに始まっているの早期導入プロセスの開始前と同様にその寿命をにもかかわらず、あなたは()関数は、デフォルトのヒープハンドルGetProcessHeapプログラムによってプロセスを取得することができますしかし、それは明示的にそれを取り消すHeapDestroy()関数を呼び出すことが許されませんでした

 

2.ヒープを動的に作成する必要性

  我々は先に述べたように、プロセスに加えて、プロセスの既定のヒープ、あなたにはすることもでき、プロセスの仮想アドレス空間に動的に作成いくつかの独立したスタック動的にプログラム設計や必要性から、別のヒープを作成するかどうかのコンポーネントを保護する必要がある場合、それはより効率的にローカルアクセスする必要があるかどうか、メモリを管理できるかどうか、およびオーバーヘッド削減の必要性とスレッドの同期があるかどうか、いくつかの原子炉や他の迅速なリリースのために必要がある考慮すべき側面が。

  (1)があるかどうかを保護コンポーネントこの原則のは比較的容易に理解することが必要です。図1、リスト(ノード構造)とツリーコンポーネント(分岐構造)と共に成分スタックの使用を示す左側面図です。(リスト成分の)数バイトの後にノード3が誤っにより書き換えられた場合は、この場合には、二成分混合スタックに格納されたデータから、それは、おそらく(ツリーに属する位置分岐に続いて2に影響を与えますコンポーネント)。これは、そのツリーをトラバースするとき、ツリーの関連するコンポーネントは、メモリの破損が原因実行することができない原因になります。その理由は、部品リストは、独自のオペレーティング・エラーが発生する確立メモリツリーによるものです。右側に示され、ツリーコンポーネントアセンブリリストが別のスタックに格納されている場合、上記の状況は明らかにエラーがアセンブリ誤操作であり、鎖に限定され、発生していない別個のツリー・コンポーネントに格納されているのでヒープおよび保護されました。

  【図1】

  図において、構成要素は12バイトを占有する場合に、異なる長さのこれらのコンポーネントのオブジェクトが(左の)スタックを共有する場合、リスト内の各ノードは、ツリーの各ブランチの16バイトを占有し、これらはに残されています割り当てられたメモリヒープが試みが24バイトの分岐目標アイドル間隔内に16バイトを割り当てる場合、ノード2および4リリース場合、フラグメントの24のバイトを有するであろうオブジェクトを充填しました割り当てるバイト数がアイドルバイト数よりも少ないが、ものの、依然として割り当てに失敗。彼らは、より効率的なメモリ管理を実装する前にのみ、スタック上のオブジェクトの同じサイズを割り当てます。ツリーは、12バイトのアセンブリの長さの他の構成要素によって置き換えられている場合、オブジェクトを解放した後、別の目的は、まさにこの新たに解放された空間オブジェクトを充填することができます。

  (2)は、ローカルアクセスが必要とするより多くの重要な原則です。システムは、しばしばますメモリとシステムページファイル間のページスワップすることが、システムのパフォーマンスを実行するための為替あまりにも多くの時間が大幅に影響される場合したがって、これらのデータが同時にアクセスされる場合は、プログラムで互いに近接した位置に割り当て、頻繁なスイッチングシステムページを回避するように設計されるべきで、システムは、メモリとページングファイル交換周波数間のページを減少させます。

  (3 スレッドの同期は、オーバーヘッドを指しオーバーヘッド複数のスレッドで費やした余分なコードを実行する必要があります破壊からアクセスしようとしながら、保護データへの順次でデフォルト状態のヒープラン。このオーバーヘッドは、スレッド・スタックの安全性を確保するために、そのためには必要であるが、ヒープ割り当て操作の数が多いため、このような追加コストが負担になり、プログラムの業績を減らしますこの余分なコストを回避するために、次のことができ、新たなヒープを作成する際に、システムがアクセスするだけで、単一のスレッドを持って知らせますセキュリティスレッドアプリケーションのこのヒープで担当する必要があります。

  (4)最後にすばやくスタックを解放する必要がある場合、それは専用にすることができ、データ構造のいくつかのスタック、およびスタック全体を解放します、未明示的にブロックごとのヒープ内の空きメモリ。ほとんどのアプリケーションでは、このプロセスがより速く実行されます

 

3.   ヒープを作成します。

  プロセスでは、必要に応じて、動的に完全に杭、HeapCreateによって()関数の元のデフォルトのヒープに基づいて作成されました:

HANDLE HeapCreate(
 DWORD flOptions、
 DWORD dwInitialSize、
 DWORD dwMaximumSize 
)。

 

  最初の引数のflOptions属性は、新しいスタックの動作を指定しますこのフラグは、このようなます。HeapAlloc()、HeapFree()、などのヒープ機能の一部に影響を与えます HeapReAlloc() などの新しいヒープへのアクセスとして、およびヒープサイズ()。可能な値は、以下のフラグの組み合わせ:

 

 

 

  参数dwInitialSize和dwMaximumSize分别为堆的初始大小和堆栈的最大尺寸。其中,dwInitialSize的值决定了最初提交给堆的字节数。如果设置的数值不是页面大小的整数倍,则将被圆整(Round Up)到邻近的页边界处。dwMaximumSize则实际上是系统能为堆保留的地址空间区域的最大字节数。如果该值为0,那么将创建一个可扩展的堆,堆的大小仅受可用内存的限制如果应用程序需要分配大的内存块,通常要将该参数设置为0。如果dwMaximumSize大于0,则该值限定了堆所能创建的最大值,HeapCreate()同样也要将该值圆整到邻近的页边界,然后再在进程的虚拟地址空间堆保留该大小的一块区域。在这种堆中分配的内存块大小不能超过0x7FFF8字节,任何试图分配更大内存块的行为将会失败,即使是设置的堆大小足以容纳该内存块

  如果HeapCreate()成功执行,将会返回一个标识新堆的句柄,并可供其他堆函数使用

  需要特别说明的是,在设置第一个参数时,对HEAP_NO_SERIALIZE的标志的使用要谨慎,一般应避免使用该标志。这是同后续将要进行的堆函数HeapAlloc()的执行过程有关系的,在HeapAlloc()试图从堆中分配一个内存块时,将执行下述几步操作:

  1) 遍历分配的和释放的内存块的链接表

  2) 搜寻一个空闲内存块的地址

  3) 通过将空闲内存块标记为"已分配"来分配新内存块

  4) 将新分配的内存块添加到内存块列表

  果这时有两个线程1、2试图同时从一个堆中分配内存块,那么线程1在执行了上面的1和2步后将得到空间内存块的地址。但是由于CPU对线程运行时间的分片,使得线程1在执行第3步操作前有可能被线程2抢走执行权并有机会去执行同样的1、2步操作,而且由于先执行的线程1并没有执行到第3步,因此线程2会搜寻到同一个空闲内存块的地址,并将其标记为已分配而线程1在恢复运行后并不能知晓该内存块已被线程2标记过,因此会出现两个线程军认为其分配的是空闲的内存块,并更新各自的联接表。显然,象这种两个线程拥有完全相同内存块地址的错误是非常严重而又是难以发现的。

  由于只有在多个线程同时进行操作时才有可能出现上述问题,一种简单的解决的办法就是不使用HEAP_NO_SERIALIZE标志而只允许单个线程独占地对堆及其联接表拥有访问权。如果一定要使用此标志,为了安全起见,必须确保进程为单线程的或是在进程中使用了多线程,但只有单个线程对堆进行访问。再就是使用了多线程,也有多个线程对堆进行了访问,但这些线程通过使用某种线程同步手段。如果可以确保以上几条中的一条成立,也是可以安全使用HEAP_NO_SERIALIZE标志的,而且还将拥有快的访问速度。如果不能肯定上述条件是否满足,建议不使用此标志而以顺序的方式访问堆,虽然线程速度会因此而下降但却可以确保堆及其中数据的不被破坏。

 

4. 从堆中分配内存块

  在成功创建一个堆后,可以调用HeapAlloc()函数从堆中分配内存块

  在此,该函数可以从两个种堆中分配内存块。(1)从用HeapCreate()创建的动态堆中分配内存块,(2)也可以直接从进程的默认堆中分配内存块。

  下面先给出HeapCreate()的函数原型

LPVOID HeapAlloc(
 HANDLE hHeap,
 DWORD dwFlags,
 DWORD dwBytes
);

 

  其中,参数hHeap为要分配的内存块来自的堆的句柄(从分配的哪个堆中进行分内存块),可以是从HeapCreate()创建的动态堆句柄也可以是由GetProcessHeap()得到的默认堆句柄。

  参数dwFlags指定了影响堆分配的各个标志。该标志将覆盖在调用HeapCreate()时所指定的相应标志,可能的取值为:

 

 

 

  最后一个参数dwBytes设定了要从堆中分配的内存块的大小。如果HeapAlloc()执行成功,将会返回从堆中分配的内存块的地址。如果由于内存不足或是其他一些原因而引起HeapAlloc()函数的执行失败,将会引发异常。通过异常标志可以得到引起内存分配失败的原因:如果为STATUS_NO_MEMORY则表明是由于内存不足引起的;如果是STATUS_Access_VIOLATION则表示是由于堆被破坏或函数参数不正确而引起分配内存块的尝试失败。以上异常只有在指定了HEAP_GENERATE_EXCEPTIONS标志时才会发生如果没有指定此标志,在出现类似错误时HeapAlloc()函数只是简单的返回NULL指针

  在设置dwFlags参数时,如果先前用HeapCreate()创建堆时曾指定过HEAP_GENERATE_EXCEPTIONS标志,就不必再去设置HEAP_GENERATE_EXCEPTIONS标志了,因为HEAP_GENERATE_EXCEPTIONS标志已经通知堆在不能分配内存块时将会引发异常。另外,对HEAP_NO_SERIALIZE标志的设置应慎重,与在HeapCreate()函数中使用HEAP_NO_SERIALIZE标志类似,如果在同一时间有其他线程使用同一个堆,那么该堆将会被破坏。如果是在进程默认堆中进行内存块的分配则要绝对禁用此标志。

  在使用堆函数HeapAlloc()时要注意:堆在内存管理中的使用主要是用来分配一些较小的数据块,如果要分配的内存块在1MB左右,那么就不要再使用堆来管理内存了,而应选择虚拟内存的内存管理机制。

 

5. 再分配内存块

  在程序设计时经常会由于开始时预见不足而造成在堆中分配的内存块大小的不合适(多数情况是开始时分配的内存较小,而后来实际需要更多的数据复制到内存块中去)这就需要在分配了内存块后再根据需要调整其大小。堆函数HeapReAlloc()将完成这一功能,其函数原型为:

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

参考:

  https://www.cnblogs.com/findumars/p/5929832.html

おすすめ

転載: www.cnblogs.com/icmzn/p/11823744.html