クラス内の隣接するメンバーの重複を防ぐものは何ですか?


12

次の3つを検討してくださいstruct

class blub {
    int i;
    char c;

    blub(const blub&) {}
};

class blob {
    char s;

    blob(const blob&) {}
};

struct bla {
    blub b0;
    blob b1;
};

intが4バイトである一般的なプラットフォームでは、サイズ、配置、およびパディングの合計1は次のとおりです。

  struct   size   alignment   padding  
 -------- ------ ----------- --------- 
  blub        8           4         3  
  blob        1           1         0  
  bla        12           4         6  

サイズ1 は原則としてのパディングに「適合」することができますが、blubblobメンバーのストレージ間に重複はありません。blobblub

C ++ 20ではno_unique_address属性が導入され、隣接する空のメンバーが同じアドレスを共有できるようになりました。また、1つのメンバーのパディングを使用して別のメンバーを格納するという上記のシナリオを明示的に許可します。cppreferenceから(私の強調):

このデータメンバーは、そのクラスの他のすべての非静的データメンバーと異なるアドレスを持つ必要がないことを示します。これは、メンバーが空の型(たとえば、ステートレスアロケーター)を持っている場合、コンパイラーがそれを最適化して、空のベースであるかのようにスペースを占有しないことを意味します。メンバーが空でない場合、その中のテールパディングも他のデータメンバーを格納するために再利用できます。

実際、この属性をで使用するblub b0と、サイズはにbla下がります8。そのため、blobは実際blub にgodboltで見られるようにに格納されます。

最後に、私は私の質問に行きます:

標準ではどのテキストが(C ++ 11からC ++ 20まで)no_unique_address簡単にコピーできないオブジェクトに対して、なしでこの重複を防ぎますか?

TCオブジェクトのstd::memcpy場合、メンバーサブオブジェクトを含む、あるオブジェクトから別のオブジェクトへの移動が許可されており、ストレージがオーバーラップしている場合、これは壊れます(ストレージのすべてまたは一部のため)。隣接するメンバーは上書きされます)2


1パディングは、構造サイズとそのすべての構成メンバーのサイズの差として再帰的に単純に計算されます。

2作るために:私はコピーコンストラクタが定義されている理由はここにあるblubblobない自明コピー可能


私はそれを研究していませんが、「まるで」のルールを推測しています。抽象マシン(コードがコンパイルされる対象)に観察可能な違い(非常に具体的な意味を持つ用語)がない場合、コンパイラーはコードを好きなように変更できます。
Jesper Juhl、

かなり確信してこれはこれのだまされやすい人です:stackoverflow.com/questions/53837373/...
NathanOliver

@JesperJuhl-そうですが、なぜそれができないのかではなくなぜそれができないのかを尋ねています。「あたかも」ルールは通常前者に適用されますが、後者には意味がありません。また、「あたかも」は、通常ローカルな問題ではなく、グローバルな問題である構造レイアウトについては明確ではありません。最終的に、コンパイラーは、おそらく「エスケープ」できないことを証明できる構造を除いて、レイアウトに関する単一の一貫した規則のセットを持つ必要があります。
BeeOnRope

1
@BeeOnRopeご質問にはお答えできません。これが、私がコメントではなく回答を投稿した理由です。あなたがそのコメントで得たのは、説明に対する私の推測でしたが、私は答えを知りません(私自身がそれを学ぶのは大変です-これがあなたが賛成票を得た理由です)。
Jesper Juhl、

1
@NicolBolas-あなたは正しい質問に答えていますか?これは、安全なコピーなどを検出するためのものではありません。むしろ、なぜメンバー間でパディングを再利用できないのか興味があります。いずれにせよ、あなたは間違っています。ささいにコピー可能、型のプロパティであり、常にされています。安全にオブジェクトをコピーするにはしかし、それは必要があり、両方の TCタイプ(型のプロパティを)持っている、と潜在的に重複被写体(私はあなたが混乱してしまったところだと思いオブジェクトのプロパティ)ではありません。なぜ私たちがここでコピーについて話しているのかまだわかりません。
BeeOnRope

回答:


1

標準は、メモリモデルについて話すときにひどく静かであり、それが使用するいくつかの用語についてあまり明確ではありません。しかし、私は実用的な議論を見つけたと思います(それは少し弱いかもしれません)

まず、オブジェクトの一部でさえあるものを見つけましょう。[basic.types] / 4

タイプのオブジェクトのオブジェクト表現Tの配列であるN unsigned charタイプのオブジェクトによって取り込まオブジェクトTNに等しいですsizeof(T)。タイプのオブジェクトの値表現は、タイプの値の表現にT関与するビットのセットですT。値表現の一部ではないオブジェクト表現のビットは、パディングビットです。

したがって、のオブジェクト表現はオブジェクトでb0構成されているsizeof(blub) unsigned charため、8バイトです。パディングビットはオブジェクトの一部です。

オブジェクトがネストされていない場合、他のオブジェクトのスペースを占有することはできません[basic.life] /1.5

oタイプのオブジェクトの存続期間は、次の場合にT終了します。

[...]

(1.5)オブジェクトが占有するストレージが解放されるか、ネストされていないオブジェクトo([intro.object])によって再利用されます。

したがって、の寿命はb0、それが占有しているストレージが別のオブジェクト、つまりによって再利用されるときに終了しますb1。私はそれをチェックしていませんが、生きているオブジェクトのサブオブジェクトも生きているべきであると標準が義務付けていると思います(これがどのように異なるように機能するか想像できませんでした)。

したがって、b0 占有しているストレージはによって使用されない可能性がありますb1。標準で「占有」の定義は見つかりませんでしたが、合理的な解釈は「オブジェクト表現の一部」になると思います。引用記述オブジェクト表現では、「取り上げる」という言葉が使用されています1。ここでは8バイトになるblaため、には少なくとももう1つ必要ですb1

特にサブオブジェクト(特に非静的データメンバー)については、[intro.object] / 9という規定もあります(ただし、これはC ++ 20で追加され、thx @BeeOnRope)

ビットフィールドではないライフタイムが重複する2つのオブジェクトは、一方が他方の中にネストされている場合、または少なくとも1つがサイズがゼロのサブオブジェクトであり、タイプが異なる場合、同じアドレスを持つ可能性があります。それ以外の場合、それらは別個のアドレスを持ち、ストレージのばらばらのバイトを占有します

(強調私の)ここでも、「占有」が定義されていないという問題があり、ここでもオブジェクト表現のバイトを取ると主張します。この[basic.memobj] / footnote 29には脚注があることに注意してください。

「as-if」ルールでは、プログラムが違いを観察できない場合([intro.execution])、実装は2つのオブジェクトを同じマシンアドレスに格納するか、オブジェクトをまったく格納しないことが許可されます。

観察可能な副作用がないことを証明できれば、コンパイラがこれを壊す可能性があります。これは、オブジェクトのレイアウトのような基本的なことについてはかなり複雑だと思います。たぶんそのため、この最適化が行われるのは、[no_unique_address]属性を追加してオブジェクトを切り離す必要がないという情報をユーザーが提供した場合のみです。

tl; dr:パディングはオブジェクトの一部である可能性があり、メンバーはばらばらにする必要があります。


1占有することを意味する可能性がある参照を追加することに抵抗できませんでした:Webster's Revised Unabridged Dictionary、G.&C. Merriam、1913(強調鉱山)

  1. の寸法を保持または埋めるため; 部屋またはスペースを取るため ; カバーまたは埋めるため; キャンプは5エーカーの地面を占めています。J.ハーシェル卿。

辞書クロールがなければ、どの標準クロールが完了しますか?


2
into.storageの「独立したストレージのバイトを占有する」部分で十分だと思いますが、この表現は、C ++ 20で追加された変更の一部としてのみ追加されましたno_unique_address。C ++ 20より前の状況はあまり明確ではありません。basic.life/1.5の「オブジェクトがネストされていない場合、オブジェクトは別のオブジェクトのスペースを占有できません」につながるあなたの推論、特に「オブジェクトが占有しているストレージが解放される」から取得する方法を理解できませんでした「オブジェクトが別のオブジェクトのスペースを占有することはできません」。
BeeOnRope

1
その段落に小さな説明を追加しました。それが理解しやすくなることを願っています。それ以外の場合は、明日もう一度見ますが、今はかなり遅いです。
n314159

「二は、ビットフィールドは一方が他方の中に入れ子になっている場合、同じアドレスを有する、またはかもしれない寿命が重複するオブジェクトの少なくとも一つはゼロサイズのサブオブジェクトであり、それらは異なるタイプである場合、」 重複寿命、の持つ2つのオブジェクト同じタイプ、同じアドレス
言語弁護士

申し訳ありませんが、詳しく説明していただけませんか?あなたは私の答えから標準的な引用を引用し、それと少し矛盾する例を持っています。これが私の回答に対するコメントであるかどうか、またそれが私に伝えるべき内容であるかどうかはわかりません。あなたの例に関しては、私は標準のさらに他の部分を考慮する必要があったと言います(別のオブジェクトにストレージを提供するunsigned char配列に関する段落があります、ゼロサイズのベース最適化に関するもの、そしてさらに新しいものも配置があるかどうかを調べる必要があります)特別な手当、私がOPの例に関連するとは思わないすべてのもの)
n314159

@ n314159この表現には欠陥があると思います。
言語弁護士
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.