フラットメモリモデル(基本的にはすべて)の実装では、キャストするuintptr_t
とJust Workになります。
(しかし、CのUBであるオブジェクトの外でポインターを形成する問題を含め、ポインターを符号付きとして扱うべきかどうかについては、64ビットx86でポインター比較を符号付きまたは符号なしにする必要があるかを参照してください。)
しかし、非フラットなメモリモデルを持つシステムが存在しませんし、それらについて考えることのために異なるスペック持つ++ Cのように、現在の状況を説明するのに役立つことができます<
対をstd::less
。
<
CでUBである(または少なくとも一部のC ++リビジョンでは指定されていない)個別のオブジェクトへのポインターの要点の1つは、非フラットメモリモデルを含む奇妙なマシンを許可することです。
よく知られている例は、x86-16リアルモードで、ポインターはセグメント:オフセットで、を介して20ビットの線形アドレスを形成します(segment << 4) + offset
。同じ線形アドレスを複数の異なるseg:offの組み合わせで表すことができます。
std::less
奇妙なISA上のポインターのC ++ は、コストがかかる可能性があります。たとえば、x86-16でセグメント:オフセットを「正規化」してオフセット<= 15にする必要があります。ただし、これを実装するポータブルな方法はありません。 a uintptr_t
(またはポインタオブジェクトのオブジェクト表現)を正規化するために必要な操作は、実装固有です。
しかし、C ++ std::less
が高価で<
なければならないシステムであっても、そうである必要はありません。たとえば、オブジェクトが1つのセグメント内に収まる「大きな」メモリモデルを想定する<
と、オフセットパーツを比較するだけで、セグメントパーツを気にする必要さえありません。(同じオブジェクト内のポインターは同じセグメントを持ち、それ以外の場合はCのUBです。C++ 17は単に「未指定」に変更されました。これにより、正規化のスキップとオフセットの比較だけが可能になる場合があります。)これは、任意の部分へのすべてのポインターを想定しています。オブジェクトは常に同じseg
値を使用し、正規化することはありません。これは、「巨大な」メモリモデルではなく「大きな」メモリモデルにABIが必要とするものです。(コメントの議論を参照)。
(たとえば、このようなメモリモデルの最大オブジェクトサイズは64kiBですが、そのような最大サイズのオブジェクトの多くに対応できる十分な最大合計アドレススペースがあります。ISOCでは、実装でオブジェクトサイズの制限を最大値(符号なし)size_t
はを表すことができSIZE_MAX
ます。たとえば、フラットメモリモデルシステムでも、GNU Cは最大オブジェクトサイズを制限しているPTRDIFF_MAX
ため、サイズ計算で符号付きオーバーフローを無視できます。この回答とコメントでの議論を参照してください。
セグメントよりも大きいオブジェクトを許可する場合はp++
、配列をループするとき、またはインデックス付け/ポインター演算を行うときに、ポインターのオフセット部分のオーバーフローを心配する必要がある「巨大な」メモリーモデルが必要です。これはどこでもコードが遅くなりますがp < q
、「巨大な」メモリモデルをターゲットとする実装は通常、常にすべてのポインタを正規化することを選択するため、異なるオブジェクトへのポインタで機能する可能性があります。近く、遠く、巨大なポインタは何ですか?を参照してください。-一部のx86リアルモード用の実際のCコンパイラには、「巨大」モデル用にコンパイルするオプションがあり、他に宣言されていない限り、すべてのポインタはデフォルトで「巨大」になります。
x86リアルモードセグメンテーションは、唯一の非フラットメモリモデルではありません。これは、C / C ++実装による処理方法を説明するのに役立つ具体例にすぎません。実際の実装では、far
vs。near
ポインタの概念を使用してISO Cを拡張し、一般的なデータセグメントを基準にして、プログラマが16ビットのオフセット部分を単に格納/渡すだけで済むようにするタイミングを選択できるようにしました。
しかし、純粋なISO Cの実装では、小さなメモリモデル(16ビットポインターを持つ同じ64kiBのコードを除くすべて)、またはすべてのポインターが32ビットである大きなメモリモデルまたは巨大なモデルのいずれかを選択する必要があります。一部のループは、オフセット部分のみをインクリメントすることで最適化できますが、ポインターオブジェクトを小さくするように最適化できませんでした。
特定の実装に対する魔法の操作がわかっている場合は、純粋なCで実装できます。問題は、異なるシステムが異なるアドレッシングを使用し、詳細が移植可能なマクロによってパラメーター化されないことです。
または、そうでない場合もあります。たとえば、アドレスのセグメント部分がインデックスであり、左シフトされる値ではないリアルモードではなく、x86プロテクトモードなど、特別なセグメントテーブルなどから何かを検索する場合があります。プロテクトモードで部分的に重複するセグメントを設定できます。アドレスのセグメントセレクタ部分は、対応するセグメントのベースアドレスと同じ順序で並べられるとは限りません。x86プロテクトモードでseg:offポインターから線形アドレスを取得するには、GDTやLDTがプロセス内の読み取り可能なページにマップされていない場合、システムコールが必要になる場合があります。
(もちろん、x86の主流のOSはフラットメモリモデルを使用しているため、セグメントベースは常に0(fs
またはgs
セグメントを使用するスレッドローカルストレージを除く)であり、32ビットまたは64ビットの「オフセット」部分のみがポインターとして使用されます。)
さまざまな特定のプラットフォーム用のコードを手動で追加できます。たとえば、デフォルトでフラットと想定する#ifdef
か、x86リアルモードを検出uintptr_t
し、16ビットの半分に分割してseg -= off>>4; off &= 0xf;
、それらの部分を32ビットの数値に結合します。