初期化されていないメンバーを持つ構造体をコピーする


29

一部のメンバーが初期化されていない構造体をコピーすることは有効ですか?

未定義の動作ではないかと思いますが、その場合、初期化されていないメンバーを構造体に残すと(それらのメンバーが直接使用されない場合でも)、非常に危険になります。だから、規格にそれを許すものがあるのか​​しら。

たとえば、これは有効ですか?

struct Data {
  int a, b;
};

int main() {
  Data data;
  data.a = 5;
  Data data2 = data;
}

しばらく前に同じような質問を見たのを覚えていますが、見つかりません。この質問はあるとして関係しているこのいずれか
1201ProgramAlarm

回答:


23

はい。初期化されていないメンバーが符号なしナロー文字タイプまたはstd::byteでない場合、暗黙的に定義されたコピーコンストラクターでこの不確定値を含む構造体をコピーすることは、同じタイプの不確定値を持つ変数をコピーするため、技術的には未定義の動作です。[dcl.init] / 12

暗黙的に生成されたコピーコンストラクターは、unions を除いて、直接初期化のように各メンバーを個別にコピーするように定義されているため、これがここに当てはまります。[class.copy.ctor] / 4を参照してください。

これは、アクティブなCWG問題2264の対象でもあります。

ただし、実際には問題はないと思います。

100%確実にしたいstd::memcpy場合は、メンバーの値が不確定であっても、型が自明にコピー可能であれば、常に明確な動作を使用できます。


これらの問題は別として、クラスに自明なデフォルトコンストラクターが必要ない場合は、常に、構築時にクラスメンバーを指定された値で適切に初期化する必要があります。これは、デフォルトのメンバー初期化構文を使用して簡単に行うことができます。たとえば、メンバーを値で初期化します。

struct Data {
  int a{}, b{};
};

int main() {
  Data data;
  data.a = 5;
  Data data2 = data;
}

まあ..その構造体はPOD(プレーンな古いデータ)ではありませんか?つまり、メンバーはデフォルト値で初期化されますか?疑問です
ケビン・コウケツ

この場合、それは浅いコピーではありませんか?コピーされた構造体で初期化されていないメンバーにアクセスしない限り、これで何が問題になるのでしょうか?
TruthSeeker

@KevinKouketsu trivial / PODタイプが必要な場合の条件を追加しました。
ウォールナット

@TruthSeeker標準では、これは未定義の動作であると述べています。(非メンバー)変数の一般に未定義の動作である理由は、AndreySemashevの回答で説明されています。基本的には、初期化されていないメモリでトラップ表現をサポートすることです。これが構造体の暗黙的なコピー構築に適用されること意図しているかどうかは、リンクされたCWG問題の問題です。
ウォールナット

@TruthSeeker暗黙的なコピーコンストラクターは、直接初期化のように各メンバーを個別にコピーするように定義されています。memcpy簡単にコピーできる型であっても、オブジェクト表現をのようにコピーすることは定義されていません。唯一の例外は共用体で、暗黙のコピーコンストラクターは、あたかものようにオブジェクト表現をコピーしますmemcpy
ウォールナット

11

一般に、初期化されていないデータのコピーは、トラッピング状態にある可能性があるため、未定義の動作です。このページの引用:

オブジェクト表現がオブジェクトタイプの値を表さない場合、それはトラップ表現と呼ばれます。文字型の左辺値式を介してそれを読み取る以外の方法でトラップ表現にアクセスすることは、未定義の動作です。

シグナルNaNは浮動小数点型で可能であり、プラットフォームによっては整数にトラップ表現がある場合があります。

ただし、自明にコピー可能な型の場合は、を使用memcpyしてオブジェクトの生の表現をコピーできます。オブジェクトの値は解釈されず、オブジェクト表現の生のバイトシーケンスがコピーされるので、そうすることは安全です。


すべてのビットパターンが有効な値を表す型のデータ(たとえば、を含む64バイトの構造体unsigned char[64])についてはどうですか?構造体のバイトをUnspecified値を持つものとして扱うと、不必要に最適化が妨げられる可能性がありますが、プログラマが手動で配列に不要な値を入力する必要があると、さらに効率が低下します。
supercat

データの初期化は役に立たないわけではなく、トラップの表現が原因であるか、後で初期化されていないデータを使用することが原因であるかに関係なく、UBを防ぎます。64バイト(1または2キャッシュライン)のゼロ化は、見かけほどコストがかかりません。また、コストがかかる大きな構造の場合は、コピーする前によく考える必要があります。そして、いずれにしても、それらを初期化する必要があると確信しています。
Andrey Semashev

プログラムの動作に影響を及ぼさない可能性のあるマシンコード操作は役に立たない。規格によってUBとして特徴付けられる行動は、[C規格委員会の言葉で] UBが「適合可能な言語拡張の可能性のある領域を特定する」と言うのではなく、絶対に避けなければならないという考えは比較的最近です。公開されたC ++標準の根拠を見たことはありませんが、プログラムを適合または非適合として分類することを拒否することで、C ++プログラムが「許可」されていることに対する管轄権を明示的に放棄します。つまり、同様の拡張が可能になります。
スーパーキャット

-1

説明されているようないくつかのケースでは、C ++標準により、コンパイラーは、動作が予測可能である必要なしに、顧客が最も有用であると思う方法で構文を処理することができます。言い換えると、このような構成は「未定義の動作」を呼び出します。ただし、C ++標準では、整形式プログラムが実行を「許可」されている対象に対する管轄を明示的に放棄しているため、そのような構成が「禁止」されることを意味しているわけではありません。C ++標準の公開されたRationaleドキュメントには気づいていませんが、C89が定義するように未定義の動作を説明しているという事実は、意図された意味が似ていることを示唆します。診断する。

何かを処理するための最も効率的な方法は、ダウンストリームコードが気にしない構造の部分を書くことを含み、ダウンストリームコードが気にしないそれらを省略する多くの状況があります。プログラムが構造体のすべてのメンバーを初期化することを要求することは、何も気にしないものも含めて、不必要に効率を妨げます。

さらに、初期化されていないデータを非決定論的な方法で動作させることが最も効率的な状況がいくつかあります。たとえば、次の場合:

struct q { unsigned char dat[256]; } x,y;

void test(unsigned char *arr, int n)
{
  q temp;
  for (int i=0; i<n; i++)
    temp.dat[arr[i]] = i;
  x=temp;
  y=temp;
}

下流のコードがの要素の値x.datまたはにy.datリストされていないインデックスの値をarr考慮しない場合、コードは次のように最適化される可能性があります。

void test(unsigned char *arr, int n)
{
  q temp;
  for (int i=0; i<n; i++)
  {
    int it = arr[i];
    x.dat[index] = i;
    y.dat[index] = i;
  }
}

プログラマがのすべての要素を明示的に書き込む必要がある場合、効率を向上させることは不可能です。これにはtemp.dat、ダウンストリームが気にしない要素も含めて、それをコピーする前に記述します。

一方、データ漏洩の可能性を回避することが重要なアプリケーションもあります。このようなアプリケーションでは、ダウンストリームコードがそれを参照するかどうかに関係なく、初期化されていないストレージをコピーする試みをトラップするように装備されたバージョンのコードを用意するか、実装がストレージを保証するように実装すると便利かもしれません内容が漏えいする可能性のあるものは、ゼロ化されるか、機密情報以外のデータで上書きされます。

私が言うことができることから、C ++標準は、これらの動作のいずれも、それを強制することを正当化するために他の動作よりも十分に有用であるとは言いません。皮肉なことに、この仕様の欠如は最適化を容易にすることを目的としている可能性がありますが、プログラマーがあらゆる種類の弱い動作保証を活用できない場合、最適化は無効になります。


-2

のすべてのメンバーはDataプリミティブ型なので、のすべてのメンバーのdata2正確な「ビットごとのコピー」を取得しdataます。したがって、の値はの値data2.bとまったく同じになりdata.bます。ただし、data.b明示的に初期化していないため、の正確な値は予測できません。これは、に割り当てられたメモリ領域のバイト値に依存しますdata


標準を参照してこれをサポートできますか?@walnutによって提供されるリンクは、これが未定義の動作であることを意味します。標準のPODに例外はありますか?
Tomek Czajka

以下は標準へのリンクではありませんが、依然として:en.cppreference.com/w/cpp/language/… "TriviallyCopyableオブジェクトは、オブジェクト表現を手動でコピーすることでコピーできます(例:std :: memmoveを使用)。Cと互換性のあるすべてのデータタイプ言語(PODタイプ)は簡単にコピーできます。」
ivan.ukr

この場合の唯一の「未定義の動作」は、初期化されていないメンバー変数の値を予測できないことです。しかし、コードは正常にコンパイルおよび実行されます。
ivan.ukr

1
あなたが引用するフラグメントはmemmoveの動作について語っていますが、私のコードではmemmoveではなくコピーコンストラクターを使用しているため、ここではあまり関係ありません。他の回答は、コピーコンストラクタを使用すると、未定義の動作が発生することを意味します。「未定義の振る舞い」という言葉も誤解されていると思います。これは、言語が保証をまったく提供しないことを意味します。たとえば、プログラムがデータをランダムにクラッシュまたは破壊したり、何かをしたりする可能性があります。これは、一部の値が予測できないことを意味するだけでなく、不特定の動作になります。
Tomek Czajka

@ ivan.ukr C ++標準では、暗黙的なコピー/移動コンストラクターが直接初期化のようにメンバーごとに動作するように指定されています。私の回答のリンクを参照してください。したがって、コピーの建設はないではない「作る『ビットごとのコピーを』」。あなたは、手動によるかのようにオブジェクト表現をコピーするために暗黙のコピーコンストラクタ指定されている共用体タイプに対してのみ正しいですstd::memcpy。これにより、std::memcpyまたはの使用が妨げられることはありませんstd::memmove。暗黙のコピーコンストラクターの使用を防ぐだけです。
クルミ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.