web-dev-qa-db-ja.com

uintptr_tがintptr_tよりも優先されるのはいつですか?

「ジェネリック」ポインタの値を構造体に格納する必要があり、ポイントされたメモリ自体には関心がないという要件を考えると、intptr_tよりもvoid*として格納する方が意味的に正しいと思います。問題は、uintptr_tがより適しているかどうか、そして一般的に一方が他方よりも優先される場合です。

17
Johann Gerell

これは主にスタイル上の議論です(最適化コンパイラはおそらく同じ、または非常に類似したコードを生成します)。ただし、ポインタの比較は難しい問題になる可能性があります。

純粋に標準的な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は、符号付き整数では適切に実行できないこと(たとえば、右にシフトするなど)をポインターのビットに対して実行したい場合に意味があります。

10
unwind

特定のシステムとプログラムに適したタイプを選択する必要があります。ほとんどの場合、ポインタは正のアドレス値です。その場合、uintptr_tが正しいタイプです。ただし、ここで説明するように、一部のシステムではカーネル空間を表現する方法として負のアドレスを使用します。 ポインタ(アドレス)が負になることはありますか? これが2つの異なるタイプがある理由です。


ジェネリックポインタ型の(u)intptr_tvoid*については、堅牢でプロフェッショナルなプログラムでは前者が推奨されます。ポインタタイプに関連する多くの問題/バグソースがあります:

  • さまざまな種類のポインタのすべての方法は、ほとんどの場合、相互に互換性がなく、エイリアスを作成できません。これは、オブジェクトポインタと関数ポインタの問題です。
  • 多くの場合、constのような型修飾子があります。これにより、その型との間のポインター変換が疑わしい、または定義が不十分になります。
  • void*およびその他のポインター型との間の変換は暗黙的に行われるため、間違ったポインター型の使用に関連するバグが気付かれずにすり抜けてしまいます。これはC++で修正されましたが、Cでは危険なままです。たとえば、古いが古典的な「C90でmallocを使用しているときにstdlib.hを含めるのを忘れた」バグを取り上げます。
  • ポインタで算術演算を実行すると、割り当てられた配列を指すポインタでしか安全に算術演算を実行できないため、多くの落とし穴があります。ただし、組み込みシステムを使用している人なら誰でも知っているように、配列を指す以外の多くの理由でメモリアドレスを持つことができます。
  • あなたvoid*でポインタ算術計算を実行することさえできません。これを行うには、非標準のコンパイラ拡張機能に依存します。

そうは言っても、レガシーコードの多くはvoidポインターに依存しており、制限されたコンテキストでそれらを使用することはまったく問題ありません。いくつかの例は、ジェネリックコールバック関数に依存する正規コードです:bsearchqsort、pthreadsなど。

ただし、新しいCプログラムを設計するときにvoidポインターを使用することはお勧めしません。私の意見では、これらは過去の危険な機能と見なされています。最近では、C11 _Generic、指定された初期化子を使用したトリック、パラメーターを配列ポインターとして(VLAに)渡す、コンパイル時にstatic_assertを使用して境界チェックを行うなど、ジェネリックCプログラミングのより優れた安全な方法があります。ここでの私の答えにいくつかの例があります: タイプセーフな列挙を作成する方法は?

4
Lundin

値を算術的に操作する(たとえば、暗号化する)場合は、符号付き型(算術オーバーフローによって未定義の動作が発生する)よりも、符号なし型(算術がラップアラウンドする)の方がはるかに柔軟性があります。

3
user2949652