このようにキャストすることは合法ですか?
void probability(void **value)
{
double v = 0.1234;
*value = (void *) *(uint64_t *) &v;
}
これは悪いことですが、ターゲット マシンでsizeof(double)
= sizeof(void *)
=が実行されることは 100% 確信しています。sizeof(uint64_t)
コードが厳密なエイリアシング規則に違反しているため、これは未定義の動作です。
コンパイラは、ほとんどの無関係な型へのポインターが同じメモリを指していないと想定することが許可されています。uint64_t *
「実際には」double であるメモリを指す (キャストの結果) を作成し、そのポインターから読み取ると、double と関係のある値が得られることを期待します。
厳密なエイリアシング ルールの目的は、コンパイラがこのコードを壊すさまざまな最適化を行えるようにすることです。最も可能性が高いv
のは、有効な名前またはポインター、無効なエイリアスのみ。
この特定のコードを確認したことはありませんが、実際には GCC は高度な最適化で厳密なエイリアシングに依存しており、この種のコードを壊します。
厳密なエイリアシングの問題を修正する方法は次のmemcpy
とおりです。
assert(sizeof(*value) == sizeof(v));
memcpy(value, &v, sizeof(*value));
これが完了した後、または厳密なエイリアシングに依存していないコンパイラ、またはその依存を回避できるコンパイラを使用している場合 ( --no-strict-aliasing
)、まだ問題があります。標準では、アドレスと同じサイズのすべての数値が実際に で表現できることを保証していませんvoid*
。たとえば、実装が (事実上) ポインターにパディング ビットを持ち、パディング ビットに間違った値でポインター値を作成しようとするとクラッシュすることは合法です。実際には、「通常」と呼ばれるハードウェアでは発生しませんが、それでも標準ではコードを許可していません。
厳密に言えば、いいえ。一部
*(uint64_t*)&v;
未定義の動作です。uint64_t
toのキャストvoid*
は有効ですが、無意味な場合があります。そのようなキャストは実装定義です (6.3.2.3)。
未定義の動作。どんなに確信があっても。