ポインター値が特定のメモリ領域にあるかどうかを確認する方法

たとえば、2つの変数を使用して定義された領域/メモリ領域があるとします。



byte* regionStart; size_t regionSize;
      
      





ポインター値がこの範囲内にあるかどうかを確認する必要があります。 おそらくあなたの最初の衝動はこれを書くことでしょう:



 if (p >= regionStart && p < regionStart + regionSize)
      
      





しかし、標準はこのコードの予想される動作を保証していますか?



C言語標準(6.5.8関係演算子)(1)の関連条項は、次のようになります。



オブジェクトまたは不完全な型への2つのポインターが同じオブジェクトまたは同じ配列の最後の要素の直後の位置を参照する場合、これらのポインターは等しくなります。 指定されたオブジェクトが同じ複合オブジェクトのメンバーである場合、後で宣言された構造体のメンバーへのポインターは、以前に宣言されたメンバーへのポインターよりも多く、より高いインデックスを持つ配列の要素へのポインターは、より低いインデックスを持つ同じ配列の要素へのポインターよりも多くなります。 同じ関連付けのメンバーへのすべてのポインターは等しいです。 式Pが配列の要素を指し、式Qが同じ配列の最後の要素を指す場合、pointer-expression Q + 1の値は式Pの値より大きくなります。他のすべての場合、動作は未定義です。



ここで、C言語は広範なアーキテクチャで動作することを目的としていたことを思い出してください。その多くはすでに博物館の展示品になっています。 このため、レガシシステム用のCプログラムを記述する機能を残す必要があるため、許容可能なアクションの選択に関しては非常に保守的です。 (かつては非常に高度でしたが。)



それにもかかわらず、メモリを割り当てると、実際には特定の領域を参照しませんが、条件を満たすポインタが表示される場合があります。 これは、たとえば、標準モードおよびOS / 2 1.xのWindows 3.xオペレーティングシステムで使用されていた保護モードの80286プロセッサで実行している場合に発生します。



このようなシステムのポインターは、16ビットの2つの部分で構成される32ビット値であり、 XXXX:YYYYのように記述するのが一般的です。 最初の16ビットの半分( XXXX )は「セレクタ」で、64 Kバイトのメモリセグメントを選択するのに役立ちます。 後半の16ビットの半分( YYYY )は「オフセット」で、前半で指定されたセグメント内でバイトが選択されます。 (実際には、このメカニズムはより複雑ですが、この議論の枠組みではこのような説明で対処します。)



64Kバイトより大きいメモリブロックは、64Kバイトのセグメントに分割されます。 次のセグメントに移動するには、現在のセグメントセレクタに8を追加します。 たとえば、 0101:FFFFに続くバイトは0109:0000として書き込まれます。



しかし、なぜ正確に8を追加する必要があるのですか? セレクターを1つだけ増やすことができないのはなぜですか? 実際、セレクターの下位3ビットは他の目的に使用されます。 特に、セレクターの最下位ビットは、セレクターのテーブルを選択するのに役立ちます。 ビット1と2は、質問に関連していないため、触れません。 便宜上、それらが常にゼロに設定されていることを想像してください(2)



セレクタと物理メモリアドレスの対応は、 グローバル記述子テーブル (すべてのプロセスに共通のメモリセグメントを定義)とローカル記述子テーブル (特定のプロセスへの個人使用のために割り当てられたメモリセグメントを定義)の2つのテーブルによって記述されます。 したがって、プロセスのローカルメモリのセレクタは0001、0009、0011、0019などであり、グローバルメモリのセレクタは0008、0010、0018、0020などです。 (セレクタ0000は予約されています。)



OK、反例を作成できます。 regionStart = 0101:0000およびregionSize = 0x00020000とします。 これは、保護されたアドレスの範囲が0101:0000〜0101:FFFFおよび0109: 0000〜0109:FFFFであることを意味します。 さらに、 regionStart + regionSize = 0111:0000



ここで、 0108:0000の範囲でグローバルメモリのセグメントが割り当てられていることを想像してください。グローバルメモリであることがセレクタの偶数を示していることを想像してください。



グローバルメモリ領域は保護されたアドレスの範囲内にないことに注意してください。ただし、このセクションへのポインターの値は不等式0101:0000?を満たします。 0108:0000 <0111:0000



もう少しテキスト:フラットメモリモデルのアーキテクチャでもテストが失敗する場合があります。 最新のコンパイラーは、未定義の動作を最適化するにはあまりにも熱心です。 ポインターの比較が見つかった場合、他のタイプの比較は未定義の動作につながるため、これらのポインターは同じ複合オブジェクトまたは配列(または配列の最後の要素の後の位置)を参照すると想定する権利があります。 この場合、 regionStartが配列または複合オブジェクトの先頭を指している場合、 regionStart、regionStart + 1、regionStart + 2、...、regionStart + regionSizeの形式のポインターのみが正しく比較できます。 それらはすべて条件p> = regionStartを満たし、したがって最適化できます。その結果、コンパイラは次のコードに対する検証を簡素化します。



 if (p < regionStart + regionSize)
      
      





これで、値がregionStartより小さいすべてのポインターによって条件が満たされます。



(元の質問の作成者として、この記事に対する回答として)regionStart = malloc(n)式を使用してメモリ領域を選択する場合、または選択した領域がクイックアクセス用の事前割り当てオブジェクトのプールとして使用され、解決する必要がある場合、この状況が発生する可能性がありますfree関数を使用してポインターを解放するかどうか。)



道徳:このコードは安全ではありません-フラットメモリモデルのアーキテクチャでもです。



しかし、すべてがそれほど悪いわけではありません。ポインターを整数型に変換した結果は、使用する実装に依存します。つまり、その動作を記述する必要があります。 実装が、ポインターが参照するオブジェクトの線形アドレスの数値の取得を伴い、フラットメモリモデルを使用するアーキテクチャで作業していることがわかっている場合、出力はポインターの代わりに整数値を比較します 。 整数の比較には、ポインターの比較のような制限はありません。



  if ((uintptr_t)p >= (uintptr_t)regionStart && (uintptr_t)p < (uintptr_t)regionStart + (uintptr_t)regionSize)
      
      





注:



  1. 「等しい」と「等しくない」は関係演算子ではないことに注意してください。
  2. 私はこれが実際にはそうではないことを知っています-ゼロに等しいので、便宜上受け入れます。


(この記事はStackOverflowに関する私のコメントに基づいてます。)



更新:明確化:「メモリ領域の開始」の最適化は、 regionStartポインターが配列または複合オブジェクトの先頭を参照する場合にのみ実行されます。



これは、「ポインターがメモリの範囲内にあるかどうかを確認する方法」のロシア語への翻訳です。 リンクをクリックして、元の英語版をご覧ください。



All Articles