私はstd::memcpy
を使用して、長い間厳密なエイリアスを回避してきました。
たとえば、 this のようなfloat
を検査します。
float f = ...;
uint32_t i;
static_assert(sizeof(f)==sizeof(i));
std::memcpy(&i, &f, sizeof(i));
// use i to extract f's sign, exponent & significand
ただし、今回は標準を確認しましたが、これを検証するものは見つかりませんでした。私が見つけたのは this :
簡単にコピー可能なタイプTのオブジェクト(潜在的に重複するサブオブジェクト以外)について、オブジェクトがタイプTの有効な値を保持しているかどうかに関係なく、オブジェクトを構成する基本バイト([intro.memory])をchar、unsigned char、またはstd :: byteの配列([cstddef.syn])。40 その配列の内容がオブジェクトにコピーされた場合、オブジェクトはその後その元の値を保持します。 [例:
#define N sizeof(T) char buf[N]; T obj; // obj initialized to its original value std::memcpy(buf, &obj, N); // between these two calls to std::memcpy, obj might be modified std::memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type holds its original value
—例を終了]
および this :
簡単にコピー可能な型Tの場合、Tへの2つのポインターが別個のTオブジェクトobj1とobj2を指し、obj1もobj2も潜在的に重複するサブオブジェクトではない場合、基礎となるバイト([intro.memory])がobj1にコピーされる場合obj2、41 obj2はその後、obj1と同じ値を保持します。 [例:
T* t1p; T* t2p; // provided that t2p points to an initialized object ... std::memcpy(t1p, t2p, sizeof(T)); // at this point, every subobject of trivially copyable type in *t1p contains // the same value as the corresponding subobject in *t2p
—例を終了]
したがって、std::memcpy
とfloat
のchar[]
のやり取りは許可され、同じ自明なタイプ間のstd::memcpy
ingも許可されます。
私の最初の例(およびリンクされた回答)は明確に定義されていますか?または、float
を検査する正しい方法は、std::memcpy
をunsigned char[]
バッファーに入れ、shift
sとor
sを使用してuint32_t
それから?
注:std::memcpy
の保証を見ても、この質問に答えられない場合があります。私の知る限り、std::memcpy
を単純なバイトコピーループに置き換えることができますが、問題は同じです。
標準では、これが許可されていると適切に言うことができない場合がありますが、ほとんどの場合、すべての実装はこれを定義済みの動作として扱います。
実際のchar[N]
オブジェクトへのコピーを容易にするために、f
オブジェクトを構成するバイトにchar[N]
であるかのようにアクセスできます。この部分は論争の対象ではないと思います。
char[N]
値を表すuint32_t
からのバイトは、uint32_t
オブジェクトにコピーできます。この部分も論争の対象ではないと思います。
同様に議論の余地のないことは、たとえばfwrite
はプログラムの1回の実行でバイトを書き込んだ可能性があり、fread
は別の実行で、または別のプログラムで完全にそれらを読み戻した可能性があります。
その最後の部分のために、uint32_t
オブジェクトの有効な表現を形成する限り、バイトがどこから来たのかは問題ではないと思います。 couldすべてのfloat
値を循環し、それぞれにmemcmp
を使用して、希望する表現が得られるまで、uint32_t
あなたがそれを解釈している値。 couldは、コンパイラが見たことのないプログラムである別のプログラムでそれを行ったことさえあります。それは有効だったでしょう。
実装の観点から、コードが明確に有効なコードと区別できない場合、コードは有効であると見なされる必要があります。
私の最初の例(およびリンクされた回答)は明確に定義されていますか?
動作は未定義ではありません(ターゲットタイプにトラップ表現がない限り)† ソースタイプで共有されていません)が、整数の結果値は実装で定義されています。標準では、浮動小数点数がどのように表現されるかについての保証はありません。そのため、整数から仮数などをポータブルな方法で抽出する方法はありません。
移植性の問題:
std::numeric_limits::is_iec559
を使用して、表現に関する仮定が正しいかどうかを確認できます。
† ただし、uint32_t
はトラップ(コメントを参照)を持つことができないため、心配する必要はありません。 uint32_t
を使用することで、難解なシステムへの移植性は既に排除されています。標準の適合システムでは、そのエイリアスを定義する必要はありません。
あなたの例は明確に定義されており、厳密なエイリアスを壊しません。 std::memcpy
は次のように明確に述べています:
Srcが指すオブジェクトからdestが指すオブジェクトに
count
バイトをコピーします。 両方のオブジェクトはunsigned char
の配列として再解釈されます
標準では、(signed/unsigned) char*
またはstd::byte
を介して任意のタイプのエイリアスを許可しているため、この例ではUBを示していません。結果の整数が任意の値である場合は別の質問ですが。
use i to extract f's sign, exponent & significand
ただし、float
の値は実装定義であるため、標準では保証されません(IEEE 754の場合は動作します)。