web-dev-qa-db-ja.com

さまざまな些細なコピー可能な型の間のstd :: memcpyは未定義の動作ですか?

私は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::memcpyfloatchar[]のやり取りは許可され、同じ自明なタイプ間のstd::memcpyingも許可されます。

私の最初の例(およびリンクされた回答)は明確に定義されていますか?または、floatを検査する正しい方法は、std::memcpyunsigned char[]バッファーに入れ、shiftsとorsを使用してuint32_tそれから?


注:std::memcpyの保証を見ても、この質問に答えられない場合があります。私の知る限り、std::memcpyを単純なバイトコピーループに置き換えることができますが、問題は同じです。

51
geza

標準では、これが許可されていると適切に言うことができない場合がありますが、ほとんどの場合、すべての実装はこれを定義済みの動作として扱います。

実際のchar[N]オブジェクトへのコピーを容易にするために、fオブジェクトを構成するバイトにchar[N]であるかのようにアクセスできます。この部分は論争の対象ではないと思います。

char[N]値を表すuint32_tからのバイトは、uint32_tオブジェクトにコピーできます。この部分も論争の対象ではないと思います。

同様に議論の余地のないことは、たとえばfwriteはプログラムの1回の実行でバイトを書き込んだ可能性があり、freadは別の実行で、または別のプログラムで完全にそれらを読み戻した可能性があります。

その最後の部分のために、uint32_tオブジェクトの有効な表現を形成する限り、バイトがどこから来たのかは問題ではないと思います。 couldすべてのfloat値を循環し、それぞれにmemcmpを使用して、希望する表現が得られるまで、uint32_tあなたがそれを解釈している値。 couldは、コンパイラが見たことのないプログラムである別のプログラムでそれを行ったことさえあります。それは有効だったでしょう。

実装の観点から、コードが明確に有効なコードと区別できない場合、コードは有効であると見なされる必要があります。

21
user743382

私の最初の例(およびリンクされた回答)は明確に定義されていますか?

動作は未定義ではありません(ターゲットタイプにトラップ表現がない限り) ソースタイプで共有されていません)が、整数の結果値は実装で定義されています。標準では、浮動小数点数がどのように表現されるかについての保証はありません。そのため、整数から仮数などをポータブルな方法で抽出する方法はありません。

移植性の問題:

  • IEEE 754はC++では保証されていません
  • Floatのバイトエンディアンが整数のエンディアンと一致することは保証されません。
  • (トラップ表現を備えたシステム)。

std::numeric_limits::is_iec559を使用して、表現に関する仮定が正しいかどうかを確認できます。

 ただし、uint32_tはトラップ(コメントを参照)を持つことができないため、心配する必要はありません。 uint32_tを使用することで、難解なシステムへの移植性は既に排除されています。標準の適合システムでは、そのエイリアスを定義する必要はありません。

18
eerorika

あなたの例は明確に定義されており、厳密なエイリアスを壊しません。 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の場合は動作します)。

14