1. ホーム
  2. c++

TriviallyCopyableでないオブジェクトに対してstd::memcpyの動作が不定になるのはなぜですか?

2023-10-22 03:57:04

質問

から http://en.cppreference.com/w/cpp/string/byte/memcpy :

もし、オブジェクトが TriviallyCopyable (例:スカラー、配列、C互換構造体)でない場合、動作は未定義です。

私の仕事場では std::memcpy を使用して TriviallyCopyable でないオブジェクトをビット単位でスワップするために長い間使用してきました。

void swapMemory(Entity* ePtr1, Entity* ePtr2)
{
   static const int size = sizeof(Entity); 
   char swapBuffer[size];

   memcpy(swapBuffer, ePtr1, size);
   memcpy(ePtr1, ePtr2, size);
   memcpy(ePtr2, swapBuffer, size);
}

で、特に問題はありませんでした。

を悪用するのは些細なことだと理解しています。 std::memcpy を悪用し、下流で未定義の挙動を引き起こすことは理解しました。しかしながら、私の質問です。

の動作はなぜ std::memcpy の動作は、TriviallyCopyable でないオブジェクトで使用された場合、未定義になるのでしょうか?なぜ規格はそれを指定する必要があると考えたのでしょうか?

アップデイト

の内容は http://en.cppreference.com/w/cpp/string/byte/memcpy は、この投稿と投稿に対する回答を受けて修正しました。現在の記述では

もし、オブジェクトが TriviallyCopyable (例えばスカラー、配列、C 互換の構造体) でない場合、プログラムがターゲットオブジェクトのデストラクタの効果に依存しない限り、動作は未定義です (このオブジェクトは memcpy によって実行されない) とターゲットオブジェクトのライフタイム (終了するが、開始しない) の効果にプログラムが依存しない限り、動作は未定義です。 memcpy によって開始されていない) の寿命は、placement-new のような他の手段によって開始されます。

PS

Cubbi さんのコメントです。

下流でUBを保証してしまうと、プログラム全体が不定になってしまうので、@RSahuさん。しかし、私はこのケースでUBを回避することが可能であるように見えることに同意し、それに応じてcppreferenceを変更しました。

どのように解決するのですか?

<ブロッククオート

の動作は、なぜ std::memcpy の動作は、TriviallyCopyableでないオブジェクトで使用された場合、不定になるのでしょうか?

それはありません! しかし、一旦、非自明なコピー可能型のあるオブジェクトの基礎となるバイトをその型の別のオブジェクトにコピーすると ターゲットオブジェクトは生きていない . 私たちはそのストレージを再利用することでそれを破壊し、コンストラクターの呼び出しによってそれを活性化させていません。

ターゲットオブジェクトを使用すること、つまりそのメンバー関数を呼び出したり、そのデータメンバーにアクセスすることは、明らかに定義されていない [基本.生活]/6 であり、その後の暗黙のデストラクタ呼び出しも同様です。 [基本的な.生活]/4 であり、自動的な保存期間を持つターゲットオブジェクトのための どのように 未定義の動作が遡及的に . [イントロ.実行]/5:

しかし,そのような実行が未定義の操作を含んでいる場合,この国際規格は,実装に対して何の要求もしない。 国際規格は、その入力でそのプログラムを実行する実装に何の要件も課さない そのプログラムをその入力で実行すること ( に関しても。 最初の未定義の操作の前の操作に関しても ).

もし実装が、あるオブジェクトが死んでいて、必然的に未定義のさらなる操作の対象となることを見抜くと、...それはあなたのプログラムのセマンティクスを変更することで反応するかもしれません。このような場合 memcpy の呼び出しからずっと。そしてこの考察は、オプティマイザーと彼らが作る特定の仮定について考えると、非常に現実的なものになります。

標準ライブラリは些細なコピー可能な型のために特定の標準ライブラリアルゴリズムを最適化することができ、また許されていることに注意する必要がありますが。 std::copy は、通常、コピー可能な型へのポインタに対して memcpy を呼び出します。ですから swap .

ですから、通常の一般的なアルゴリズムを使うことに専念し、適切な低レベルの最適化はコンパイラに任せましょう。これは、そもそも些細にコピー可能な型のアイデアが考案された理由の一部です。ある種の最適化の合法性を判断するためです。また、これにより、言語の矛盾や仕様が定まっていない部分について心配する必要がなくなり、脳を傷つけずに済みます。