一部の動的型付け言語では、表現される値の実行時の型を識別または絞り込むための簡単な方法として、ポインターのタグ付けを使用します。これを行う古典的な方法は、ポインターを適切なサイズの整数に変換し、整列されたオブジェクトに対してゼロであると想定される最下位ビットにタグ値を追加することです。オブジェクトにアクセスする必要がある場合、タグ ビットはマスクされ、整数はポインターに変換され、ポインターは通常どおり逆参照されます。
これだけでも問題ありませんが、すべてが 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_t
6.3.2.3 パラグラフ 6 で示唆されている未定義の動作は無関係ですよね?)
これらすべてが当てはまる場合、アラインされた配列の要素への変換されたポインターの下位ビットに実際に依存して、Cell
自由にタグ付けできることを意味すると思います。彼らは && しますか?
(私が知る限り、この質問は仮説です。なぜなら、通常の仮定は一般的なプラットフォームに当てはまります.そうでないものを見つけた場合、おそらくC標準ではなくガイダンスに目を向けたくないでしょう. platform docs; しかし、それは重要ではありません。)