INT_MAX
( 64ビットアーキテクチャ )私たちはそれを苦しみ、私たちの製品は失敗する運命にあります。
状況を防ぐことは難しいことではないようです-「すべてを所定の場所に置く」ルールを使用しますが、実際には、たとえば例外を使用するため、ヒューマンファクター(banal不注意)、アーキテクチャのarchitectureさ、およびオペレーター実行の非線形順序によって非常に複雑になります
また、パフォーマンスを犠牲にして自動ガベージコレクターに「降伏」することができます(これは必ずしもマネージC ++ではありません。ネイティブC ++ / Cにはガベージコレクションライブラリなどがあります)。
ただし、「すべてが悪い」状況については説明します。
その場合、タスクは潜在的なリークを検出して修正することになります-修正に関しては、ここではすべてが簡単です(
delete
または
delete[]
)。 しかし、リークを検出する方法は? グーグルは答えを喜んで教えてくれるでしょう。
ただし、デバッグCRTを使用すると、より簡単に実行できます。
ステップ1.リーケージアカウンティングを有効にする
これを行うには、Debug CRTヘッダーを接続し、Debug Heap Alloc Mapの使用を有効にします。
#ifdef _DEBUG
#include <crtdbg.h>
#define _CRTDBG_MAP_ALLOC
#endif
* This source code was highlighted with Source Code Highlighter .
さて、
new
または
malloc()
を介してメモリを割り当てる
malloc()
データは次の構造にラップされます( しかし、実際には少しずるいです、データを担当するフィールドは
struct
の構文に対応せず、「構造体」自体はCRT内のどこかで定義され、その説明はプログラマ向けではありません利用可能 ):
typedef struct _CrtMemBlockHeader
{
struct _CrtMemBlockHeader * pBlockHeaderNext;
struct _CrtMemBlockHeader * pBlockHeaderPrev;
char * szFileName;
int nLine;
size_t nDataSize;
int nBlackUse;
long lRequest;
unsigned char gap[nNoMansLandSize];
unsigned char data[nDataSize];
unsigned char anotherGap[nNoMansLandSize];
} _CrtMemBlockHeader;
* This source code was highlighted with Source Code Highlighter .
これには、
szFileName
ファイル
szFileName
と、メモリが割り当てられた
nDataSize
、要求された
nDataSize
メモリの量、および実際には、いわゆるNo Mans Landエリアにラップされたデータ
data
に関する情報が含まれます。
BlockHeader
自体
BlockHeader
二重にリンクされたリストに編成されているため、リストを簡単に作成でき、それに応じて、対応するリリース操作がなかったすべてのメモリ割り当て操作を識別できます。
ステップ2.リークのリスト
CrtMemBlockHeader
リストを
CrtMemBlockHeader
て、問題のある領域に関する情報を提供する関数が必要です。
_CrtDumpMemoryLeaks();
次に、[デバッグ出力]ウィンドウに次の情報が表示されます。
Detected memory leaks!
Dumping objects ->
{163} normal block at 0x00128788, 4 bytes long.
Data: < > 00 00 00 00
{162} normal block at 0x00128748, 4 bytes long.
Data: < > 00 00 00 00
Object dump complete.
* This source code was highlighted with Source Code Highlighter .
そして、それはほとんどクールですが、この結果はいくつかの理由でまだ使用できません:
- ファイルに関する情報と、メモリが割り当てられた行に関する情報は含まれていません(ただし、構造にはそのような情報があります!)
- 私はそれを何らかのログファイルに出力したい(少なくとも自動化)
- 冗長な情報が含まれています。つまり、既に「リーク」したメモリだけでなく...
...また、単にグローバルオブジェクトから「戻る」時間がなかったもの。 そして、グローバルオブジェクトはおそらく悪いかもしれませんが、今ではそれらがそうであるという考えに慣れましょう。つまり、何らかの方法でそれらを
_CrtDumpMemoryLeaks()
出力から削除する必要があるということです。 そして、これは次のトリックによって解決されます。
int _tmain( int argc, _TCHAR* argv[])
{
_CrtMemState _ms;
_CrtMemCheckpoint(&_ms);
// some logic goes here...
_CrtMemDumpAllObjectsSince(&_ms);
return 0;
}
* This source code was highlighted with Source Code Highlighter .
初期(メインに入る時点の)メモリ状態(
_CrtMemCheckpoint
)を特別な構造に書き込み、アプリケーションを終了する前に、
_ms
後に作成されたメモリ内の残りのオブジェクト(
_CrtMemDumpAllObjectsSince
)を表示します。これらは「リーク」です。 これで情報が正しいので、その利便性を考慮します。
ステップ3.結果の表示
出力のリダイレクトは非常に簡単です。ここでは、
_CrtSetReportMode
および
_CrtSetReportFile
関数が役立ちます。
_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
_CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT );
* This source code was highlighted with Source Code Highlighter .
これで、すべての警告の出力(
_CrtMemDumpAllObjectsSince
出力)がstdoutに直接
_CrtMemDumpAllObjectsSince
れます。
_CrtSetReportFile
関数の2番目のパラメーターは、実際のファイルハンドルに設定できます。
メモリ割り当てが発生したファイル名と行が表示されないのはなぜですか? Microsoft Visual C ++ 6.0によると、
crtdbg.h
ヘッダーの
new
関数の次の再定義がこの情報に関与していることが
crtdbg.h
ました。
#ifdef _CRTDBG_MAP_ALLOC
inline void * __cdecl operator new (unsigned int s)
{ return :: operator new (s, _NORMAL_BLOCK, __FILE__, __LINE__); }
#endif /* _CRTDBG_MAP_ALLOC */
* This source code was highlighted with Source Code Highlighter .
そして、推測するのは難しくありません。目的の結果が得られませんでした
__FILE__:__LINE__
常に「crtdbg.h file line 512」にデプロイされていました 。 そして、Microsoftのスタッフがこの「機能」を完全に削除し、プログラマに提供しました。 1つの定義でこの機能を実現できるため、それは怖いことではありません。
#define new new ( _NORMAL_BLOCK, __FILE__, __LINE__)
* This source code was highlighted with Source Code Highlighter .
一般的なヘッダーファイルに含めることを強くお勧めします(
crtdbg.h
後に含める必要があります)。
new
が既にオーバーライドされている場合、問題が発生します。 私が見ているように、newのいくつかの合理的な再定義はCRTを使用しません(そうでなければフック技術を使用することは可能です)、そしてこの場合、スキームはまったく適用されません、大丈夫です。
一般的に、今では彼らは望んでいたものを手に入れました。 ここに結論の例がありますが、どうあるべきかはすでに明らかです。
計算時間
もちろん、CRT Internals構造の編成とサポートには時間がかかり、追加のメモリが必要です。 いくらですか?
UPD:以下はすべてWin32でのみ有効です(Vista SP1でテスト済み)。
new
(理論的には40Mbのメモリ)を使用して1000万のintを作成します。
CRTのデバッグ | 〜500Mb | 3秒 |
リリース | 〜160Mb | 1秒 |
リリースビルドの〜160Mbの数値は、少し驚かれるかもしれません。 しかし、これは正常です
HeapAlloc
は、複数の16アドレスにデータを整列させる
HeapAlloc
OS関数を介してメモリを割り当てます(Win32の場合)。 1文字のメモリを割り当てると、さらに15バイトが得られます。これを使用して、何か悪いこともできます(確かに行う必要はありません)。 デバッグの場合、非常に予測可能な結果-別の
sizeof(_CrtMemBlockHeader)
に1000万を掛けて追加すると、正確に500メガバイトになります。
興味深い経験的な結果により、このリリースでは、
new int
は
HeapAlloc
よりも4バイト遅くなり、速度は
new int()
(デフォルトで初期化、つまりゼロ)とほとんど区別できず、5
HeapAlloc
速くなりますフラグ
HEAP_ZERO_MEMORY
HeapAlloc
した
HeapAlloc
よりも-10%
さて、今では
new int[256]
(理論的には128Mbのメモリ)を経由して、128千int [256]:
CRTのデバッグ | 〜136Mb | 172ミリ秒 |
リリース | 〜128.5Mb | 60ミリ秒 |
結果は予測可能であり、非常に満足のいくものです。 さまざまなデータを混合し、メモリを部分的に解放する場合を含む、サイズが異なるデータでも1:3の速度比が確認されました。 ただし、動的メモリ操作をデバッグしないと、コードはリリースよりも数倍遅くなります!
おわりに
メモリリークは素手で対処できます。 もちろん、「生の」出力は、リークツリーや、リーク全体の降順で並べ替えられたコードプレースのリストほど効果的ではありません(ただし、これはすべて、私たちの結論によると、難なく生成できます)。 しかし、小規模なプロジェクトやタスクでは、このトリックを行うことができます。 また、このメソッドはサポートを必要とせず(
new
の再定義のために「書かれて忘れられた」わけではありませんが、それに近い )、エントリーレベルは本格的なアナライザーよりもはるかに低くなっています。
まあ、おそらくそれだけです。 それが全体像の再構築の源ですか?
UPD:メソッドは、外部アナライザーと競合しません。 目標は多少異なりますが、スタンディングツールの言及は大歓迎です(繰り返しはせず、お願いします)。