「ジェネリック」ポインタの値を構造体に格納する必要があり、ポイントされたメモリ自体には関心がないという要件を考えると、intptr_t
よりもvoid*
として格納する方が意味的に正しいと思います。問題は、uintptr_t
がより適しているかどうか、そして一般的に一方が他方よりも優先される場合です。
これは主にスタイル上の議論です(最適化コンパイラはおそらく同じ、または非常に類似したコードを生成します)。ただし、ポインタの比較は難しい問題になる可能性があります。
純粋に標準的なCポインター比較では、同じ集計データへのポインターに対してのみ大まかに意味があることを忘れないでください。 malloc
からの2つの結果を比較することはおそらく許可されていません。ポインタのソートされた配列を保持します。
私はそれらを_void*
_として、あるいは_uintptr_t
_として保持します。署名された_intptr_t
_には、負の数と正の数を分離するのに不便があり、それらが重要なアプリケーションポインターからのものである場合、これはおそらく歓迎されません。
_void*
_は逆参照できないことに注意してください。_uintptr_t
_として、データを処理するためにキャストする必要があります(== --- ==)アドレスが指す;ただし、_void*
_ポインタはmemset
のようなルーチンに渡すことができます
PS。フラットな仮想アドレス空間を備えた通常のプロセッサ(x86、PowerPC、ARMなど)を想定しています。エキゾチックなプロセッサ(おそらくいくつかのDSP)を見つけることができますが、非常に大きな違いがあります(そして、おそらく_intptr_t
_が常に意味があるとは限りません。1990年代には Cray Y-MP スーパーコンピュータsizeof(long*) != sizeof(char*)
;当時 C99 は存在せず、その_<stdint.h>
_がそのようなマシンで意味があるかどうかはわかりません)
キャストが必要になるので、それは非常に奇妙に聞こえます。 A void *
in Cには、キャストなしで他のオブジェクトポインタタイプとの間で変換するという大きな利点があります。これは非常にクリーンです。
それはuintptr_t
は、符号付き整数では適切に実行できないこと(たとえば、右にシフトするなど)をポインターのビットに対して実行したい場合に意味があります。
特定のシステムとプログラムに適したタイプを選択する必要があります。ほとんどの場合、ポインタは正のアドレス値です。その場合、uintptr_t
が正しいタイプです。ただし、ここで説明するように、一部のシステムではカーネル空間を表現する方法として負のアドレスを使用します。 ポインタ(アドレス)が負になることはありますか? これが2つの異なるタイプがある理由です。
ジェネリックポインタ型の(u)intptr_t
とvoid*
については、堅牢でプロフェッショナルなプログラムでは前者が推奨されます。ポインタタイプに関連する多くの問題/バグソースがあります:
const
のような型修飾子があります。これにより、その型との間のポインター変換が疑わしい、または定義が不十分になります。void*
およびその他のポインター型との間の変換は暗黙的に行われるため、間違ったポインター型の使用に関連するバグが気付かれずにすり抜けてしまいます。これはC++で修正されましたが、Cでは危険なままです。たとえば、古いが古典的な「C90でmallocを使用しているときにstdlib.hを含めるのを忘れた」バグを取り上げます。void*
でポインタ算術計算を実行することさえできません。これを行うには、非標準のコンパイラ拡張機能に依存します。そうは言っても、レガシーコードの多くはvoid
ポインターに依存しており、制限されたコンテキストでそれらを使用することはまったく問題ありません。いくつかの例は、ジェネリックコールバック関数に依存する正規コードです:bsearch
、qsort
、pthreadsなど。
ただし、新しいCプログラムを設計するときにvoid
ポインターを使用することはお勧めしません。私の意見では、これらは過去の危険な機能と見なされています。最近では、C11 _Generic
、指定された初期化子を使用したトリック、パラメーターを配列ポインターとして(VLAに)渡す、コンパイル時にstatic_assert
を使用して境界チェックを行うなど、ジェネリックCプログラミングのより優れた安全な方法があります。ここでの私の答えにいくつかの例があります: タイプセーフな列挙を作成する方法は? 。
値を算術的に操作する(たとえば、暗号化する)場合は、符号付き型(算術オーバーフローによって未定義の動作が発生する)よりも、符号なし型(算術がラップアラウンドする)の方がはるかに柔軟性があります。