web-dev-qa-db-ja.com

配列のベクトルのメモリレイアウトは何ですか?

誰でもメモリレイアウトを説明できますか

std::vector<std::array<int, 5>> vec(2)

それは5要素の2行で2D配列の連続したメモリブロックを提供しますか?

私の理解では、ベクトルのベクトル

std::vector<std::vector<int>> vec(2, std::vector<int>(5))

twoのメモリレイアウトを提供します長さの連続した配列5要素 sメモリ内の別の場所

配列のベクトルについても同じでしょうか?

46
Const

配列には間接性はありませんが、データを「直接」格納するだけです。つまり、std::array<int, 5>には文字どおり5つのintsがフラットに並んでいます。また、ベクトルと同様に、要素間にパディングを挿入しないため、「内部的に隣接」しています。

ただし、 std::arrayオブジェクト自体は、その要素のセットよりも大きくなる場合があります !パディングのような末尾の「もの」を持つことは許可されています。したがって、可能性は高いですが、最初のケースでデータがallで連続しているとは限りません。

An int
+----+
|    |
+----+

A vector of 2 x int
+----+----+----+-----+        +----+----+
| Housekeeping | ptr |        | 1  |  2 |
+----+----+----+-----+        +----+----+
                   |          ^
                   \-----------

An std::array<int, 5>
+----+----+----+----+----+----------->
| 1  |  2 |  3 |  4 |  5 | possible cruft/padding....
+----+----+----+----+----+----------->

A vector of 2 x std::array<int, 5>
+----+----+----+-----+        +----+----+----+----+----+----------------------------+----+----+----+----+----+----------->
| Housekeeping | ptr |        | 1  |  2 |  3 |  4 |  5 | possible cruft/padding.... | 1  |  2 |  3 |  4 |  5 | possible cruft/padding....
+----+----+----+-----+        +----+----+----+----+----+----------------------------+----+----+----+----+----+----------->
                   |          ^
                   \-----------

そして、たとえそうであったとしても、エイリアシングルールにより、単一のint*を使用して10個の数値すべてをナビゲートできるかどうかは、別の問題になる可能性があります。

全体として、10個のintsのベクトルは、より明確で完全にパックされており、使用するのにより安全です。

ベクトルのベクトルの場合、ベクトルは実際には単なるポインタにハウスキーピングを加えたものであり、したがって間接的です(あなたが言うように)。

std::vectorstd::arrayの大きな違いは、std::vectorにはラップするメモリへのポインタが含まれているのに対し、std::arrayには実際の配列自体が含まれていることです。

つまり、ベクトルのベクトルは ギザギザの配列 のようなものです。

配列のベクトルの場合、std::arrayオブジェクトは隣接して配置されますが、ベクトルオブジェクトから分離されます。 std::arrayオブジェクト自体は、それらが含む配列よりも大きい場合があることに注意してください。その場合、データは連続しません。

最後のビットは、std::arrayの配列(プレーンCスタイルまたはstd::array)もデータを連続的に保持しない可能性があることを意味します。配列内のstd::arrayオブジェクトは連続していますが、データは連続していません。

「多次元」配列の連続データを保証する唯一の方法は、ネストされたプレーンCスタイルの配列です。

18

C++標準では、std::arrayのペイロードが配列の最後に含まれていないことを保証していないため、後続の配列の最初の要素が前の配列の最後の要素の直後にあるとは限りません。

その場合でも、別の配列内の要素へのポインターのポインター演算によって配列内の任意の要素に到達しようとするときの動作は定義されていません。これは、ポインタ演算が配列内でのみ有効であるためです。

上記はstd::array<std::array>にも適用されます。

11
Bathsheba
_static_assert(sizeof(std::array<int,5>)==5*sizeof(int));
_

上記は、_std::array_の末尾にパディングがないことを緩和します。主要なコンパイラーが上記をこの日付まで失敗させることはなく、私は将来はそうしません。

上記が失敗した場合に限り、std::vector<std::array<int,5>> v(2)は_std::array_ sの間に「ギャップ」があります。

これはあなたが望むほど助けにはなりません。次のように生成されたポインタ:

_int* ptr = &v[0][0];
_

_ptr+5_までの有効範囲のみがあり、_ptr+5_の逆参照は未定義の動作です。

これは、エイリアシングルールによるものです。最初に特定の型(_char*_など)へのラウンドトリップを行わず、制限の少ないポインター演算が許可されている場合を除いて、あるオブジェクトの終わりを越えて別のオブジェクトに「ウォーク」することはできません。 。

そのルールは、任意のポインター演算で外部オブジェクトに到達できることを証明する必要なく、コンパイラーがどのポインターを介してどのデータがアクセスされているかを推論できるようにするために存在します。

そう:

_struct bob {
  int x,y,z;
};

bob b {1,2,3};
int* py = &b.y;
_

_int*_としてpyをどのように使用しても、cannotxまたはzを合法的に変更できます。

_*py = 77;
py[-1]=3;
std::cout << b.x;
_

_std::cout_が_1_を変更するattemptかもしれないので、コンパイラは_py[-1]=3_行を最適化して、単純に_b.x_を出力できますが、これにより未定義の動作です。

同じ種類の制限により、_std::vector_の最初の配列から2番目の配列に移動することができません(つまり、_ptr+4_を超えて)。

_ptr+5_の作成は合法ですが、1つの最後のポインタとしてのみです。 _ptr+5 == &v[1][0]_の比較も、バイナリ値がすべての主要なハードウェアシステムのすべてのコンパイラで完全に同一であっても、結果では指定されません。

ウサギの穴をさらに掘り下げたい場合、C++自体に_std::vector<int>_を実装することさえできません。これは、ポインターのエイリアスに関するこれらの制限のためです。最後に確認しました(これは c ++ 17 の前でしたが、C++ 17では解決策が見られませんでした)標準委員会がこれを解決するために取り組んでいましたが、状態はわかりませんそのような努力の。 (これは、標準準拠のC++に_std::vector<int>_を実装する必要がないので、あなたが考えているほどの問題ではありません。単純に標準定義の動作を持つ必要があります。コンパイラ固有の拡張機能を内部で使用できます。)