web-dev-qa-db-ja.com

整数<->ポインタキャストが実際に正しいのはいつですか?

一般的な民間伝承は次のように述べています。

  • 型システムが存在するのには理由があります。整数とポインタは異なるタイプであり、それらの間のキャストはほとんどの場合不正行為であり、設計エラーを示している可能性があるため、回避する必要があります。

  • このようなキャストが実行された場合でも、整数とポインターのサイズについては想定されません(void*intにキャストするのが、x64でコードを失敗させる最も簡単な方法です)。 intintptr_tからuintptr_tまたはstdint.hを使用する必要があります。

それを知って、実際にいつ役立つのかそのようなキャストを実行するのですか?

(注:移植性の代償としてコードを少し短くしても、「実際に役立つ」とは見なされません。)


私が知っている1つのケース:

  • 一部のロックフリーマルチプロセッサアルゴリズムは、2バイト以上の位置に配置されたポインタに冗長性があるという事実を利用しています。次に、ポインタの最下位ビットをブールフラグとして使用します。適切な命令セットを備えたプロセッサを使用すると、ロックメカニズムが不要になる場合があります(ポインタとブールフラグが分離している場合に必要になります)。
    (注:この方法は、Java via Java.util.concurrent.atomic.AtomicMarkableReference)

他に何かありますか?

77
Kos

整数が何らかの形でハッシュサムの一部である必要がある場合、整数へのポインターをキャストすることがあります。また、それらを整数にキャストして、ポインターに常に1つまたは2つのスペアビットが残っていることが保証されている特定の実装でビットをいじります。ここで、追加のポインターを使用する代わりに、左/右ポインターでAVLまたはRBツリー情報をエンコードできます。メンバー。しかし、これはすべて実装固有であるため、一般的なソリューションとして考えないことをお勧めします。また、そういうことでハザードポインタを実装できることもあるそうです。

状況によっては、オブジェクトごとに一意のIDが必要です。私のリクエストIDとしてサーバー。メモリを節約する必要がある状況に応じて、それだけの価値がある場合は、オブジェクトのアドレスをそのようなIDとして使用し、通常は整数にキャストする必要があります。

組み込みシステム(Canonカメラなど、chdkを参照)で作業する場合、魔法のアデスがよくあるので、_(void*)0xFFBC5235_などもよく見られます

編集:

pthread_self()に(私の心の中で)つまずいただけで、通常はtypedefであるpthread_tが符号なし整数に返されます。内部的には、問題のスレッドを表すスレッド構造体へのポインタです。一般的に、それは不透明なハンドルのために他の場所で使用されるかもしれません。

38
PlasmaHH

SIGBUS/SIGSEGVだけでなく、アサーションでミスアラインメントされたメモリがキャッチされるように、一般的にタイプのアラインメントをチェックするときに役立つ場合があります。

例えば。:

#include <xmmintrin.h>
#include <assert.h>
#include <stdint.h>

int main() {
  void *ptr = malloc(sizeof(__m128));
  assert(!((intptr_t)ptr) % __alignof__(__m128));
  return 0;
}

(実際のコードでは、mallocを賭けるだけでなく、要点を示しています)

15
Flexo

半分のスペースを使用して二重にリンクされたリストを保存する

XORリンクリスト は、次のポインタと前のポインタを同じサイズの単一の値に結合します。これは、2つのポインターを一緒に排他的論理和することによって行われます。これには、それらを整数のように扱う必要があります。

12
Craig Gidney

私の頭の中で最も有用なケースは、プログラムをはるかに効率的にする可能性があるケースです。多くの標準および共通のライブラリインターフェイスは、単一のvoid *引数を取り、それをいくつかのコールバック関数に返します。ソート。コールバックが大量のデータを必要とせず、単一の整数引数のみを必要とするとします。

関数が戻る前にコールバックが発生する場合は、ローカル(自動)int変数のアドレスを渡すだけで、すべて問題ありません。しかし、この状況の最良の実例はpthread_createです。この場合、「コールバック」は並行して実行され、pthread_createが戻る前にポインターを介して引数を読み取ることができるという保証はありません。この状況では、3つのオプションがあります。

  1. malloc単一のintで、新しいスレッドを読み取ってfreeします。
  2. intと同期オブジェクト(セマフォやバリアなど)を含む呼び出し元ローカル構造体へのポインターを渡し、pthread_createを呼び出した後に呼び出し元に待機させます。
  3. intvoid *にキャストし、値で渡します。

オプション3は、他の選択肢のいずれよりも非常に効率的であり、どちらも追加の同期ステップが含まれます(オプション1の場合、同期はmalloc/freeで行われ、ほぼ確実にいくつかが含まれますスレッドの割り当てと解放は同じではないため、コストがかかります)。

8
R..

1つの例はWindowsです。 SendMessage()およびPostMessage()関数。それらは、HWnd(ウィンドウへのハンドル)、メッセージ(整数型)、およびメッセージの2つのパラメーター、WPARAMLPARAMを取ります。どちらのパラメータータイプも不可欠ですが、送信するメッセージによっては、ポインターを渡す必要がある場合があります。次に、LPARAMまたはWPARAMへのポインターをキャストする必要があります。

私は一般的に疫病のようにそれを避けます。ポインタを格納する必要がある場合は、可能であればポインタ型を使用してください。

8
Rudy Velthuis

組み込みシステムでは、レジスタがメモリマップの固定アドレスにあるメモリマップドハードウェアデバイスにアクセスするのが非常に一般的です。私はよくハードウェアをCとC++で異なる方法でモデル化します(C++を使用すると、クラスとテンプレートを利用できます)が、一般的な考え方は両方に使用できます。

簡単な例:ハードウェアにタイマーペリフェラルがあり、2つの32ビットレジスタがあるとします。

  • 固定レート(マイクロ秒ごとなど)でデクリメントするフリーランニングの「ティックカウント」レジスタ

  • タイマーの開始、タイマーの停止、カウントをゼロにデクリメントしたときのタイマー割り込みの有効化などを可能にする制御レジスタ。

(実際のタイマー周辺機器は通常、かなり複雑であることに注意してください)。

これらの各レジスタは32ビット値であり、タイマペリフェラルの「ベースアドレス」は0xFFFF.0000です。次のようにハードウェアをモデル化できます。

// Treat these HW regs as volatile
typedef uint32_t volatile hw_reg;

// C friendly, hence the typedef
typedef struct
{
  hw_reg TimerCount;
  hw_reg TimerControl;
} TIMER;

// Cast the integer 0xFFFF0000 as being the base address of a timer peripheral.
#define Timer1 ((TIMER *)0xFFFF0000)

// Read the current timer tick value.
// e.g. read the 32-bit value @ 0xFFFF.0000
uint32_t CurrentTicks = Timer1->TimerCount;

// Stop / reset the timer.
// e.g. write the value 0 to the 32-bit location @ 0xFFFF.0004
Timer1->TimerControl = 0;

このアプローチには100のバリエーションがあり、その長所と短所は永遠に議論される可能性がありますが、ここでのポイントは、整数をポインターにキャストする一般的な使用法を説明することだけです。このコードは移植性がなく、特定のデバイスに関連付けられており、メモリ領域が立ち入り禁止ではないことを前提としていることに注意してください。

6
Dan

コンパイラとプラットフォームの組み合わせの動作を完全に理解していて、それを利用したい場合を除いて、このようなキャストを実行することは決して役に立ちません(質問シナリオはそのような例の1つです)。

私がそれが決して役に立たないと言う理由は、一般に、あなたはコンパイラーの制御を持っておらず、コンパイラーがどのような最適化を行うかについての完全な知識を持っていないからです。言い換えれば、生成されるマシンコードを正確に制御することはできません。したがって、一般的に、この種のトリックを安全に実装することはできません。

3

ポインタをintに格納するのはいつ正しいですか?それをそのまま扱うと正しいです:プラットフォームまたはコンパイラ固有の動作の使用。

問題は、プラットフォーム/コンパイラ固有のコードがアプリケーション全体に散らばっていて、コードを別のプラットフォームに移植する必要がある場合にのみ発生します。これは、もはや当てはまらない仮定を行っているためです。そのコードを分離し、基盤となるプラットフォームについて何も想定しないインターフェイスの背後に隠すことで、問題を解消します。

実装を文書化する限り、ハンドルなどを使用してプラットフォームに依存しないインターフェイスの背後で分離し、テストされたプラットフォーム/コンパイラでのみ条件付きでコードをコンパイルします。うまくいくなら、あなたが出くわすどんな種類のブードゥー魔法も使わない理由はありません。必要に応じて、アセンブリ言語の大きなチャンク、独自のAPI呼び出し、カーネルシステム呼び出しを含めることもできます。

とはいえ、「ポータブル」インターフェイスが整数ハンドルを使用する場合、整数は特定のプラットフォームの実装のポインターと同じサイズであり、その実装は内部でポインターを使用します。単にポインターを整数ハンドルとして使用しないのはなぜですか。その場合、ある種のハンドル/ポインタールックアップテーブルの必要性を排除するため、整数への単純なキャストは理にかなっています。

2
James O'Doherty

pointerintegerにキャストするのは、ポインターを格納したいときだけですが、使用できるストレージは整数だけです。

2
Ian Boyd

x64では、onはタグ付けにポインターの上位ビットを使用できます(実際のポインターには47ビットしか使用されないため)。これは、ランタイムコード生成(コメントによると、LuaJITは古代の手法であるこの手法を使用します)のようなものに最適です。このタグ付けとタグチェックを行うには、キャストまたはunionが必要です。基本的に同じことです。

整数へのポインタのキャストは、ビニングを利用するメモリ管理システムでも非常に役立ちます。つまり、数学を介してアドレスのビン/ページを簡単に見つけることができます。これは、私がしばらく書いたロックレスアロケータの例です。バック:

inline Page* GetPage(void* pMemory)
{
    return &pPages[((UINT_PTR)pMemory - (UINT_PTR)pReserve) >> nPageShift];
}
1
Necrolis

固定の既知のアドレスでメモリにアクセスする必要がある場合があります。その場合、アドレスは整数であり、ポインタに割り当てる必要があります。これは、組み込みシステムではやや一般的です。逆に、メモリアドレスを出力する必要があるため、整数にキャストする必要がある場合があります。

ああ、そしてポインタをNULLに割り当てて比較する必要があることを忘れないでください。これは通常、0Lのポインタキャストです。

1
deStrangis

私は、オブジェクトのネットワーク全体のIDでそのようなことを1つの用途があります。このようなIDは、マシンのID(IPアドレスなど)、プロセスID、およびオブジェクトのアドレスを組み合わせます。ソケットを介して送信するには、そのようなIDのポインター部分を、前後の転送に耐えられるように十分に広い整数に入れる必要があります。ポインター部分は、これが意味をなすコンテキスト(同じマシン、同じプロセス)、他のマシン、または他のプロセスでは、異なるオブジェクトを区別するためだけに機能するポインター(=ポインターにキャストバック)としてのみ解釈されます。

その働きをするために必要なのは存在ですuintptr_tおよびuint64_t固定幅整数型として。 (最大64個のアドレスを持つマシンでのみ機能します:)

1
Jens Gustedt

配列をバイトごとに調べようとしているときに、このようなシステムを使用しました。多くの場合、ポインタは一度に複数のバイトを歩くため、診断が非常に難しい問題が発生します。

たとえば、intポインタ:

int* my_pointer;

移動my_pointer++は、4バイト進みます(標準の32ビットシステムの場合)。ただし、移動((int)my_pointer)++それを1バイト進めます。

(char *)へのポインタをキャストする以外は、これが実際にそれを達成する唯一の方法です。 ((char*)my_pointer)++

確かに、(char *)は、より理にかなっているので、私の通常の方法です。

0
Richard

ポインター値は、乱数ジェネレーターをシードするためのエントロピーの便利なソースにもなります。

int* p = new int();
seed(intptr_t(p) ^ *p);
delete p;

ブーストUUIDライブラリは、このトリックと他のいくつかのトリックを使用します。

0
Inverse

オブジェクトへのポインタをタイプレスハンドルとして使用するという古くて良い伝統があります。たとえば、フラットCスタイルのAPIを使用して2つのC++ユニット間の相互作用を実装するために使用する人もいます。その場合、ハンドル型は整数型の1つとして定義され、どのメソッドも、パラメーターの1つとして抽象型のないハンドルを期待する別のメソッドに転送する前に、ポインターを整数に変換する必要があります。さらに、循環依存関係を解消する他の方法がない場合もあります。

0
Sergey Shamov