12

一部の動的型付け言語では、表現される値の実行時の型を識別または絞り込むための簡単な方法として、ポインターのタグ付けを使用します。これを行う古典的な方法は、ポインターを適切なサイズの整数に変換し、整列されたオブジェクトに対してゼロであると想定される最下位ビットにタグ値を追加することです。オブジェクトにアクセスする必要がある場合、タグ ビットはマスクされ、整数はポインターに変換され、ポインターは通常どおり逆参照されます。

これだけでも問題ありませんが、すべてが 1 つの巨大な仮定にかかっていることを除けば、整列されたポインターは適切な場所に 0 ビットがあることが保証された整数に変換されるということです。

規格の文字に従ってこれを保証することは可能ですか?


標準セクション 6.3.2.3 (参照は C11 ドラフトへの参照) は、ポインターから整数への変換の結果は実装定義であると述べていますが、私が疑問に思っているのは、6.5.2.1 および 6.5.6 のポインター算術規則が効果的かどうかです。多くのプログラムがすでに想定しているのと同じ予測可能な算術規則に従うように、ポインターから整数への変換の結果を制約します。(6.3.2.3 ノート 67 は、これがとにかく標準の意図された精神であることを示唆しているように見えますが、それはあまり意味がありません。)

私は特に、動的言語のヒープとして機能する大きな配列を割り当てる可能性がある場合を考えています。したがって、私たちが話しているポインターは、この配列の要素です。私は、C割り当て配列自体の開始を、何らかの二次的な手段によって整列された位置に配置できると想定しています(ただし、これについてもぜひ議論してください)。8 バイトの「コンス セル」の配列があるとします。特定のセルへのポインターが、タグ用に空いている最下位 3 ビットの整数に変換されることを保証できますか?

例えば:

typedef Cell ...; // such that sizeof(Cell) == 8
Cell heap[1024];  // such that ((uintptr_t)&heap[0]) & 7 == 0

((char *)&heap[11]) - ((char *)&heap[10]); // == 8
(Cell *)(((char *)&heap[10]) + 8);         // == &heap[11]
&(&heap[10])[0];                           // == &heap[10]
0[heap];                                   // == heap[0]

// So...
&((char *)0)[(uintptr_t)&heap[10]];        // == &heap[10] ?
&((char *)0)[(uintptr_t)&heap[10] + 8];    // == &heap[11] ?

// ...implies?
(Cell *)((uintptr_t)&heap[10] + 8);        // == &heap[11] ?

(私が正しく理解している場合、実装が提供する場合、uintptr_t6.3.2.3 パラグラフ 6 で示唆されている未定義の動作は無関係ですよね?)

これらすべてが当てはまる場合、アラインされた配列の要素への変換されたポインターの下位ビットに実際に依存して、Cell自由にタグ付けできることを意味すると思います。彼らは && しますか?

(私が知る限り、この質問は仮説です。なぜなら、通常の仮定は一般的なプラットフォームに当てはまります.そうでないものを見つけた場合、おそらくC標準ではなくガイダンスに目を向けたくないでしょう. platform docs; しかし、それは重要ではありません。)

4

2 に答える 2

19

これだけでも問題ありませんが、すべてが 1 つの巨大な仮定にかかっていることを除けば、整列されたポインターは適切な場所に 0 ビットがあることが保証された整数に変換されるということです。

規格の文字に従ってこれを保証することは可能ですか?

実装でこれを保証することは可能です。ポインターを整数に変換した結果は実装定義であり、標準の要件を満たしている限り、実装は好きなように定義できます。

標準は、一般的にこれを絶対に保証しません。

具体的な例: 私は Cray T90 システムで作業しました。このシステムでは、C コンパイラが UNIX ライクなオペレーティング システムで実行されていました。ハードウェアでは、アドレスは 64 ビット ワードのアドレスを含む 64 ビット ワードです。ハードウェア バイト アドレスはありませんでした。バイト ポインター ( void*char*) は、64 ビット ワード ポインターの未使用の上位 3 ビットに 3 ビット オフセットを格納することにより、ソフトウェアで実装されました。

ポインターからポインター、ポインターから整数、および整数からポインターへのすべての変換は、単に表現をコピーするだけでした。

つまり、整数に変換された場合、8 バイトでアラインされたオブジェクトへのポインターは、その下位 3 ビットに任意のビット パターンを持つことができます。

標準にはこれを禁止するものはありません。

結論:現在のシステムがポインターをどのように表現するかについて特定の仮定を行う場合、現在のシステムでそれらの仮定が有効である限り、ポインター表現でゲームをプレイする、あなたが説明したようなスキームは機能します。

しかし、そのような仮定は 100% 信頼できるものではありません。なぜなら、標準はポインターがどのように表現されるかについて何も述べていないからです (ポインター型ごとに固定サイズであり、表現が の配列として表示できること以外はunsigned char)。

(標準では、すべてのポインターが同じサイズであることさえ保証されていません。)

于 2013-09-02T17:47:09.793 に答える
5

あなたは標準の関連部分について正しいです。参考のため:

整数は、任意のポインター型に変換できます。前に指定された場合を除き、結果は実装定義であり、正しく配置されていない可能性があり、参照された型のエンティティを指していない可能性があり、トラップ表現である可能性があります。

任意のポインター型を整数型に変換できます。前に指定された場合を除き、結果は実装定義です。結果が整数型で表現できない場合、動作は未定義です。結果は、任意の整数型の値の範囲内にある必要はありません。

変換は実装定義であるため (整数型が小さすぎる場合を除き、その場合は未定義です)、この動作について標準が教えてくれることは何もありません。実装によって必要な保証が得られれば、準備は完了です。そうでなければ、残念です。

あなたの明確な質問に対する答えを推測します:

規格の文字に従ってこれを保証することは可能ですか?

標準はこの動作をパントし、実装がそれを定義する必要があると言っているため、「はい」です。おそらく、「いいえ」も同じ理由で適切な答えです。

于 2013-09-02T17:37:15.453 に答える