3

次のような標準レイアウト メンバを持つ標準レイアウト クラスがあるとします。

struct foo {
    int n;
    int m;
    unsigned char garbage;
};

標準によれば、 と のメモリ領域に書き込むことなく構造体の最後のバイトに書き込むことは常に安全nですかm(ただし、 に書き込む可能性がありgarbageます)? 例えば、

foo f;
*(static_cast<unsigned char *>(static_cast<void *>(&f)) + (sizeof(foo) - 1u)) = 0u;

C++11 標準を読むのに時間を費やした後、答えは「はい」のように思えます。

9.2/15 から:

同じアクセス制御 (第 11 節) を持つ (非共用体) クラスの非静的データ メンバーは、後のメンバーがクラス オブジェクト内でより高いアドレスを持つように割り当てられます。アクセス制御が異なる非静的データメンバーの割り当て順序は規定されていません (11)。実装のアライメント要件により、隣接する 2 つのメンバーが互いの直後に割り当てられない場合があります。仮想関数 (10.3) および仮想基本クラス (10.1) を管理するためのスペースの要件も同様です。

したがって、メンバーのアドレスは他の 2 つのメンバー (標準レイアウトであるため、連続して格納されます) よりも高く、したがって、構造体の最後のバイトは最終パディングにgarbage属するか、その一部である必要があります。garbage

この推論は正しいですか?fここでオブジェクトの有効期間をいじっていますか? パディングバイトへの書き込みは問題ですか?

編集

コメントへの返信として、ここで達成しようとしているのは、私が作成しているバリアントのようなクラスと関係があります。

単純な方法 (つまり、格納されている型を記録するために int メンバーをバリアント クラスに配置する) で処理を進めると、パディングによってクラスが必要以上に 50% 近く大きくなります。

私がやろうとしているのは、バリアントに保存しようとしている各クラス型の最後のすべてのバイトが書き込み可能であることを確認することです。これにより、ストレージ フラグを raw ストレージ (整列された raw char 配列) に組み込むことができます。バリアント。私の特定のケースでは、これにより無駄なスペースのほとんどが排除されます。

編集2

実際の例として、次の 2 つのクラスが一般的な 64 ビット マシンのバリアントに格納されているとします。

// Small dynamic vector class storing 8-bit integers.
class first {
    std::int8_t    *m_ptr;
    unsigned short m_size_capacity; // Size and capacity packed into a single ushort.
};

// Vector class with static storage.
class second {
    std::int8_t  m_data[15];
    std::uint8_t m_size;
};

class variant
{
    char m_data[...] // Properly sized and aligned for first and second.
    bool m_flag; // Flag to signal which class is being stored.
};

これら 2 つのクラスのサイズは、私のマシンでは 16 です。バリアント クラスで必要な余分なメンバーにより、サイズは 24 になります。最後にガベージ バイトを追加すると、次のようになります。

// Small dynamic vector class storing 8-bit integers.
class first {
    std::int8_t    *m_ptr;
    unsigned short m_size_capacity; // Size and capacity packed into a single ushort.
    unsigned char  m_garbage;
};

// Vector class with static storage.
class second {
    std::int8_t    m_data[14]; // Note I lost a vector element here.
    std::uint8_t   m_size;
    unsigned char  m_garbage;
};

両方のクラスのサイズは 16 のままですが、各クラスの最後のバイトを自由に使用できるようになった場合、バリアントのフラグ メンバーを廃止でき、最終的なサイズは 16 のままです。

4

3 に答える 3

9

代わりに、タグを最初に配置し、その後に他の小さなメンバーを配置する必要があります。

// Small dynamic vector class storing 8-bit integers.
struct first
{
    unsigned char  m_tag;
    std::uint8_t   m_size;
    std::uint8_t   m_capacity;
    std::int8_t    *m_ptr;
};

// Vector class with static storage.
struct second
{
    unsigned char  m_tag;
    std::uint8_t   m_size;
    std::int8_t    m_data[14];
};

次に、言語規則により、これらを に入れ、unionいずれかを使用して にアクセスできますm_tag。これは、ユニオンの「アクティブな」メンバーでなくても、初期レイアウトが同じであるためです (メンバーの一般的な初期シーケンスの特別な規則)。

union tight_vector
{
     first dynamic;
     second small_opt;
};

tight_vector v;
if (v.dynamic.m_size < 4) throw std::exception("Not enough data");
if (v.dynamic.m_tag == DYNAMIC) { /* use v.dynamic */ }
else { /* use v.small_opt */ }

問題のルールは 9.2/18 です。

標準レイアウト共用体に、共通の初期シーケンスを共有する 2 つ以上の標準レイアウト構造体が含まれている場合、および標準レイアウト共用体オブジェクトにこれらの標準レイアウト構造体のいずれかが現在含まれている場合、任意の共通の初期部分を検査することが許可されます。そのうちの。2 つの標準レイアウト構造体は、対応するメンバーがレイアウト互換の型を持ち、どちらのメンバーもビット フィールドではないか、両方が 1 つ以上の初期メンバーのシーケンスに対して同じ幅のビット フィールドである場合、共通の初期シーケンスを共有します。

于 2013-09-02T01:54:35.807 に答える
1

はい、C++ では、クラスを含むすべてのオブジェクトは、一連のアドレス指定可能なcharオブジェクト (「バイト」) として表され、アクセス指定子を介在させずにクラスで順番に宣言されたオブジェクトは、順番に昇順のアドレスを持ちます。したがって、 のストレージには、またはよりもgarbage高いアドレス ( としてアドレス指定されている場合char *) が必要です。nm

理論的には、コンパイラはオブジェクトの最後に基底クラス、または vtable ポインターのようなものを格納できますが、実際には、単純にするために、そのようなものは常に先頭に配置されます。標準レイアウトクラスのサイズについて標準が何を保証しているかはわかりません。これは、パディングを追加できるかどうかに関係し、実装が何らかの目的でパディングの存在に依存できるかどうかに影響しますが、おそらくたまたまそこにあるパディングの使用が許可されている実装に行き着きますが、追加することはできません(とにかくアクセスはおそらく単純でも効率的でもありません)。

私がやろうとしているのは、バリアントに格納しようとしている各クラス型の最後のすべてのバイトが書き込み可能であることを確認することです

garbageこれは、メンバー自体を使用する場合とどのように異なりますか? そこにあることがわかっている場合は、おそらく簡単にアクセスできます。

于 2013-09-02T01:47:26.177 に答える
0

構造体の最後のバイトは n と m の一部ではありませんが、コンパイラが型情報など、最後のバイトに何か他のものを格納していないことをどのように確認できますか?

そのようなことを保証する標準を思い出せません。sizeof(T) の memcpy の入出力が同じ値になるということだけです。これは、最終バイトに情報が保持されないという意味ではありません。

于 2013-09-02T01:46:21.180 に答える