ポインターが適切に機能しなかったときに発生する問題に直面しました:配列から出てバッファーをオーバーフローさせ、誤って未知のメモリに書き込み、次にこの「ガベージ」を別の場所で読み取り、場合によってはシステム全体がクラッシュする可能性があります 時々、それは単なる「ゲーム」です! そして、この「ゲーム」に正しく対処できるようにする必要があります。そのようなエラーや問題を見つけて修正するのに間に合います。 これは、Intelの「プラス」コンパイラが数リリース前に行ったこととまったく同じです。 さらに、多くのアイデアがさらに進められ、
Intel Memory Protection Extensionsを介してハードウェアに実装されます。 これがコンパイラでどのように機能するか見てみましょう。
「コード内ですぐにポインターを使用してエラーを検出し、修正し、出力でデバッグされた動作中のアプリケーションを提供するようなコンパイラーオプションがあればいいと思います」と、ある開発者は夢を見ました。 実際、これは計画されておらず、計画されていないようです。 Intelコンパイラは、コードを動的にチェックする手段のみを提供します。 これは、通常どおり、マジックオプションの1つを接続し、コードを収集して実行するためにアプリケーションを実行する必要があることを意味します。その間、エラーが発生します。 それがすべてです。 詳しくは、このように見えます。
ポインターチェッカー機能を使用すると、アプリケーション全体のポインターを使用して、メモリーの処理をキャッチできます。 これを行うために、各ポインターには下限と上限の許容境界があり、メモリを操作するときにチェックされ、正しい動作が保証されます。 当然、この情報はあるアドレスの特別なプレートに保存されます。 その中で、ポインターの下限
(下限)および上限
(upper_bound)の値を見つけることができます。
最も単純な場合、
pに
malloc(size)を介してメモリを割り当てた場合、
lower_bound(p)にはアドレス
(char *)pがあり、
upper_bound(p)にはアドレス
(lower_bound(p)+ size-1)があります。 そして、最も些細な例では、これは問題を検出します:
char * buffer = (char*)malloc(5); for (int i = 0; i <= 5; i++) buffer[i] = 'A' + i;
配列には5つの要素しかありません。許容上限を超えるアドレスに書き込もうとすると、範囲外になるというランタイムエラーが発生します。
次のようになります。
CHKP: Bounds check error ptr=0X012062ED sz=1 lb=0X012062E8 ub=0X012062EC loc=0X0 0131149 Traceback: wmain [0x131149] in file C:\ConsoleApplication1.cpp at line 12 __tmainCRTStartup [0x13F959] in file f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c at line 623 wmainCRTStartup [0x13FA9D] in file f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c at line 466 BaseThreadInitThunk [0x76D3919F] RtlInitializeExceptionChain [0x77550BBB] RtlInitializeExceptionChain [0x77550B91] CHKP Total number of bounds violations: 1
明らかに、私たちのポインターは範囲外でした。
ptr値は
0x012062EDであり、上限は
ub (
0x012062EC)以下でなければなりません。 そうすることで、トレースバックを取得し、問題箇所を簡単に見つけることができます。 これはすべて、
C / C ++->コード生成->ポインターのチェックタブで Visual Studioから設定できる
Qcheck-pointersキー(Windows)を使用してアプリケーションをコンパイルした条件下で発生し
ます 。 Linuxの場合、
-check-pointerスイッチを使用し
ます 。 あなたがあまりにも怠wereでなく、正直にWindowsの下でVSインターフェースを通してそれを公開しようとしたなら、あなたはおそらく異なるPointer Checkerの動作モードがあることに気づいたでしょう:
- 読み取りおよび書き込みの境界を確認します(/ Qcheck-pointers:rw)
- 書き込み専用の境界を確認する(/ Qcheck-pointers:write)
- Intel MPX(/ Qcheck-pointers-mpx:rw)を使用して、読み取りと書き込みの境界を確認します
- Intel MPXを使用して、書き込み専用の境界を確認します(/ Qcheck-pointers-mpx:write)
最後の2つのオプションは、アプリケーションの実際の起動時に何も提供しません。使用するためのハードウェアがまだ利用できないためです。 実際、ソフトウェアの機能が少し前に現れるのは一般的な習慣です。 AVXなど、他のテクノロジーでも同じことが起こります。
私たちにとって興味深いのは、読み取り操作と書き込み操作の両方で、書き込み時のみにポインターをチェックする機能です。
Qcheck-pointers:writeオプションを使用すると、読み取り操作中に
バッファーポインターの設定範囲を超えてもエラーは発生しません。 たとえば、この場合、配列が正しく初期化されている場合:
for (int i = 0; i <= 5; i++) printf("%c", buffer[i]);
Qcheck-pointers:rwキーでコンパイルした
後 、読み取りを含むすべてのケースをキャッチします。 ところで、関数にポインタを渡すと、境界に関する情報も保存されます。
もう1つの興味深い機能があります。メモリを操作するという概念と、ポインターを使用する単純な算術を区別できる必要があります。 例:
char *p = (char *)malloc(100); p += 200; p[-101] = 0; p[0] = 0;
最初の式では、ポインタを移動するだけで、有効な領域にあるメモリを参照します-この場合の
p [-101]は
p [99]です。 したがって、すべてがスムーズに進みます。 実際に
p [200]に書き込もうとしているため、範囲外に出るエラーは最後の行でのみ発生します。
ダングリングポインターを見つけるための特別なアルゴリズムがあり、
Qcheck-pointers-danglingオプションが使用されます(
Qcheck-pointersと一緒に指定する必要があります)。 これらは、メモリがすでにクリアされている場合であり、ポインターを使用して継続的に何かを実行しようとしています。 バッファの例を続けると、このカテゴリの何か:
free(buffer); printf("%c", buffer[2]);
ただし、
Qcheck-pointers-danglingを追加インストールしないと
、このケースはエラーとは見なされません。
Qcheck-pointers-danglingを登録すると、コンパイラーは、
空き関数と
削除演算子に特別なラッパーを使用します。 クリアされたメモリを持つすべてのポインタを検出し、下限を2に設定し、上限を0に設定します。したがって、このポインタを使用してメモリを操作しようとすると、エラーが発生します。 検討した例では、エラーは次のようになります(コンパクト化のためにトレースバック情報が削除されています)。
CHKP: Bounds check error ptr=0X007F62EA sz=1 lb=0X00000002 ub=00000000 loc=0X00DB11D5
ところで、メモリを操作するための関数の独自の実装がある場合、
chkp.hで宣言された
__chkp_invalidate_dangling関数を呼び出すことで、その中にハンギングポインターをチェックする関数を含めることができます。
メモリをクリアする関数の例は次のようになります。
#include <chkp.h> void my_free(void *ptr) { size_t size = my_get_size(ptr); // do the free __chkp_invalidate_dangling(ptr, size); }
結論として、Pointer Checkerには多くの可能性があり、これらすべてをペンで試す必要があると思います。 たとえば、1つ以上のモジュールをポインターチェック付きでコンパイルし、他のモジュールを選択せずにコンパイルすることが可能です。 さらに、より柔軟な操作のために、多くのAPI関数を使用できます。 Pointer CheckerはWindowsおよびLinuxでのみサポートされ、Macではサポートされていないことに注意してください。
コインの裏側もあります-アプリケーションの実行は大幅に遅くなります(少なくとも2回、ただし5回以下)。 当然、コードのサイズも大きくなります。 それでも、アプリケーションのデバッグを目的とする場合、機能は非常に興味深いものであり、ハードウェアに実装することにより、すべてがより効率的になります。
そして最後に、小さな質問です。 この全体がマルチスレッドアプリケーションでどのように機能すると思いますか? ポインターにアクセスするたびに、境界に関する情報の読み取りまたは書き込みを行い、いくつかの命令を費やし、異なるポインターが同じポインターに対して異なる情報を書き込もうとするという事実を考えます。