C++は、オブジェクトとそのパディングフィールドをゼロに初期化できる言語構造をサポートしていますか。cppreference.comで、次のような励みになる言葉を見つけました。ゼロ初期化これは、条件によっては、パディング バイトもゼロにされる可能性があることを示しています。
cppreference.com からの引用:ゼロ初期化
ゼロ初期化は次の状況で実行されます。
- 非クラス型およびコンストラクターを持たない値初期化クラス型のメンバーの値初期化シーケンスの一部として、初期化子が提供されていない集約の要素の値初期化を含みます。
ゼロ初期化の効果は次のとおりです。
- T がスカラー型の場合、オブジェクトの初期値は T に明示的に変換された整数定数ゼロになります。
- T が非共用体クラス型の場合、すべての基本クラスと非静的データ メンバーはゼロ初期化され、すべてのパディングはゼロ ビットに初期化されます。コンストラクターは、存在する場合は無視されます。
- ...
ゼロ初期化に関する言及は、値の初期化、集合初期化そしてリストの初期化。
かなり最新の GCC および clang C++ コンパイラを使用してテストしましたが、それらの動作は異なるようです。
率直に言って、私はこれらのルールを解析するのに一生懸命努力しましたが、特にコンパイラの動作が異なることを考えると、これらのルールを正しく解釈する方法がわかりませんでした。
コードを見るここ(最低でも C++11 が必要です)。結果は次のとおりです。
与えられたもの: Foo
struct Foo
{
char x;
int y;
char z;
};
構築する | g++ | クラング++ |
---|---|---|
フー() | x:[----][0x42][0x43][0x44],v: 0 |
x:[----][----][----][----],v: 0 |
y:[----][----][----][----],v: 0 |
y:[----][----][----][----],v: 0 |
|
z:[----][0x4A][0x4B][0x4C],v: 0 |
z:[----][----][----][----],v: 0 |
|
フー{} | x:[----][----][----][----],v: 0 |
x:[----][0x42][0x43][0x44],v: 0 |
y:[----][----][----][----],v: 0 |
y:[----][----][----][----],v: 0 |
|
z:[----][----][----][----],v: 0 |
z:[----][0x4A][0x4B][0x4C],v: 0 |
ここでは[----]
すべてのビットが 0 であるバイトを表し、これ[0x..]
はガベージ値です。
ご覧のとおり、コンパイラの出力はパディングが初期化されていないことを示しています。 とFoo()
は両方ともFoo{}
値の初期化です。 さらに はFoo{}
初期化子がない集合体の初期化です。 ゼロ初期化ルールがトリガーされないのはなぜですか? パディング ルールがトリガーされないのはなぜですか?
パディング バイトをゼロにすることを前提とするのは良い考えではない、あるいは未定義の動作になる可能性もあることはすでに理解していますが、それはこの質問の趣旨から外れていると思います。
- 質問 1: 標準では、パディング バイトを確実に初期化する方法が提供されていますか?
- 質問2: 以下も参照してください:Cは構造体のパディングを初期化しますか?. 適用できますか?
- 質問 3: これらのコンパイラは標準に準拠していますか?
- 質問 4: コンパイラの明らかに異なる動作の説明は何ですか?
ベストアンサー1
アップデート:
私の回答と以下のコメントは、そもそもパディングが特定の状態にあることが観察可能な動作にとって意味があること、つまり、オブジェクトまたはそれが属する完全なオブジェクトに他の変更がない場合、オブジェクトのパディングは指定された状態のままになり、潜在的にその値で読み戻すことができることを前提としていることを明確にする必要があります。
しかし、標準ではパディングの動作については実質的に何も述べられておらず、WG21 から見つけた情報によると、標準の現在の理解では、C++ ではパディングは常に未指定の値を持つようです。したがって、パディングをゼロにするかどうかを尋ねるのは無意味です。どのような形式で読み戻しても、再びゼロを生成する必要がないため、コンパイラーはパディングを初期化するための要件を事実上無視できます。
特に、これは C とは異なります。C では、パディングが未指定の値を取る可能性がある条件と、パディングが安定している必要がある条件が標準で明示的に指定されています。
例えば、CWG 2536。
引用文で述べられているように、クラス オブジェクトがゼロ初期化されている場合にのみ、パディング ビットがゼロになります。
自動および動的ストレージ期間オブジェクトの場合、ゼロ初期化は、オブジェクトが値初期化され、削除されていない暗黙のデフォルト コンストラクターがあり、他のユーザー提供のデフォルト コンストラクターがない場合にのみ発生します。[dcl.init.general]/8.1ここでこれらの条件が満たされます。
値の初期化は常に()
初期化子で行われるべきです。([dcl.init.general]/16.4)
初期化子として値の初期化が行われることもあります{}
。ただし、クラスがここでのように集約である場合は、集約の初期化が優先され、値の初期化は行われません。([dcl.init.list]/3.4)
集約初期化を値初期化よりも優先する設定は、CWG 1301C++14 より前ですが、C++11 にも適用される可能性があります。C++11 より前はルールが異なっていた可能性がありますが、確認していません。
Foo()
したがって、Clang は正しく動作しており、GCC は不必要な作業を行っている点で間違っていると言えますFoo{}
(ただし、@PeterCordes が以下で指摘しているように、パディングを含むオブジェクト全体をゼロにする方が実際には効率的です)。
ゼロで初期化されていないパディング バイトの値を検査することが、あなたが行っているような方法で明確に定義された動作であるかどうかは、私には完全には明らかではないことに注意してください。
デフォルトで初期化された場合、メンバーの読み取りは値が不確定になるため、未定義の動作になります。
パディングも、new
初期化される前には不確定な値を持つものと想定されます。その場合、ゼロ初期化がない場合に値を検査すると、未定義の動作が発生します。