std::vectorのヒープ確保の挙動に関して、実験した結果をメモしておく。
あくまで後述の簡単なコードを走らせたときの話に限るため、その他大規模だったり複雑な条件下においても同じ結論となる保証は無いことを断っておく。
gcc (libstdc++), clang (libc++), msvc いずれにおいても同様の挙動になることを確認
結論
表題のとおり、clear()
を呼んでも内部的にデータを保持しておくためのヒープ領域は再確保されることがなく、今までの領域が使い回されるようだった。つまりdata()
(=確保されているヒープ領域への生ポインタ)やcapacity()
(=領域のサイズ)の戻り値は変化しなかった。
#include <iostream>
#include <vector>
int main() {
std::vector<int> v(0);
auto dump = [&]() {
std::cout << "要素数: " << v.size() << ", キャパシティ: " << v.capacity() << ", ポインタ: " << v.data() << "\n";
};
dump();
for (int i = 0; i < 17; i++) {
v.push_back(i);
dump();
}
v.clear();
dump();
for (int i = 0; i < 17; i++) {
v.push_back(i);
dump();
}
}
実行結果は次のようになる(Clangにて)。
要素数: 0, キャパシティ: 0, ポインタ: (nil)
要素数: 1, キャパシティ: 1, ポインタ: 0x5ecd172daec0
要素数: 2, キャパシティ: 2, ポインタ: 0x5ecd172daee0
要素数: 3, キャパシティ: 4, ポインタ: 0x5ecd172daec0
要素数: 4, キャパシティ: 4, ポインタ: 0x5ecd172daec0
要素数: 5, キャパシティ: 8, ポインタ: 0x5ecd172daf00
要素数: 6, キャパシティ: 8, ポインタ: 0x5ecd172daf00
要素数: 7, キャパシティ: 8, ポインタ: 0x5ecd172daf00
要素数: 8, キャパシティ: 8, ポインタ: 0x5ecd172daf00
要素数: 9, キャパシティ: 16, ポインタ: 0x5ecd172daf30
要素数: 10, キャパシティ: 16, ポインタ: 0x5ecd172daf30
要素数: 11, キャパシティ: 16, ポインタ: 0x5ecd172daf30
要素数: 12, キャパシティ: 16, ポインタ: 0x5ecd172daf30
要素数: 13, キャパシティ: 16, ポインタ: 0x5ecd172daf30
要素数: 14, キャパシティ: 16, ポインタ: 0x5ecd172daf30
要素数: 15, キャパシティ: 16, ポインタ: 0x5ecd172daf30
要素数: 16, キャパシティ: 16, ポインタ: 0x5ecd172daf30
要素数: 17, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 0, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 1, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 2, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 3, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 4, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 5, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 6, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 7, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 8, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 9, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 10, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 11, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 12, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 13, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 14, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 15, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 16, キャパシティ: 32, ポインタ: 0x5ecd172daf80
要素数: 17, キャパシティ: 32, ポインタ: 0x5ecd172daf80
(追記)MSVCでの実行結果
要素数: 0, キャパシティ: 0, ポインタ: 0000000000000000
要素数: 1, キャパシティ: 1, ポインタ: 0000025D7A197150
要素数: 2, キャパシティ: 2, ポインタ: 0000025D7A1A3970
要素数: 3, キャパシティ: 3, ポインタ: 0000025D7A1A3E70
要素数: 4, キャパシティ: 4, ポインタ: 0000025D7A1A37E0
要素数: 5, キャパシティ: 6, ポインタ: 0000025D7A1A2230
要素数: 6, キャパシティ: 6, ポインタ: 0000025D7A1A2230
要素数: 7, キャパシティ: 9, ポインタ: 0000025D7A1A28F0
要素数: 8, キャパシティ: 9, ポインタ: 0000025D7A1A28F0
要素数: 9, キャパシティ: 9, ポインタ: 0000025D7A1A28F0
要素数: 10, キャパシティ: 13, ポインタ: 0000025D7A1A5DF0
要素数: 11, キャパシティ: 13, ポインタ: 0000025D7A1A5DF0
要素数: 12, キャパシティ: 13, ポインタ: 0000025D7A1A5DF0
要素数: 13, キャパシティ: 13, ポインタ: 0000025D7A1A5DF0
要素数: 14, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 15, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 16, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 17, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 0, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 1, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 2, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 3, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 4, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 5, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 6, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 7, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 8, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 9, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 10, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 11, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 12, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 13, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 14, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 15, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 16, キャパシティ: 19, ポインタ: 0000025D7A19A610
要素数: 17, キャパシティ: 19, ポインタ: 0000025D7A19A610
17個の要素を追加後、clear()
を実行する前後でキャパシティとポインタは変化していないことが分かる。また再度要素を追加していってもこれらは変化しない。
その他、clangでは要素を追加するたびにキャパシティが2の累乗の形で増加することが確認できる。
一方で、msvcではまた異なるキャパシティとなるようである。
実ケースとしては例えばOpenGLで、std::vector
で管理している配列をVRAM(VBO)へコピーしたい際に実ポインタが必要になる。このポインタ(data()
で取得できる)がいつ変化するのかを知っておくと何かと制御が利く。
(追記)実装依存なのか
頂いたコメントを踏まえ、この結果の普遍性を調べてみた。
私が今回gccとclang([追記] & msvc)で実行した限りではclean()
はメモリを解放しないという挙動が見られたものの、これが規格として定められている挙動なのかは疑わしい。
規格書の中で該当するであろう箇所を見つけ出してざっと眺めてみたが、メモリ確保や再確保のタイミングなどに関しては特に定められていないように思われる。気になるのはpush_back()
の備考において「新しいサイズが古い容量より大きい場合に再割り当てが行われる」のような文言があることだが、これを拡大解釈して「std::vector
は必要が生じたときのみメモリを再確保し、必要ない場合はできるだけ再確保の発生を抑える」とまで読み取るのは難しいかも。まあ実装は普通そうなっているだろうけど(有り難いことに)。
私が読み取れた範囲だけではあるが総合すると、途中で書いた"メモリが2の累乗サイズで確保されていく"というのも含め、メモリ確保に関しては規格としてはあまり定まっておらず、各実装に委ねられているようだった。
「でも大体はそういう実装だよね?」とも思ってしまいそうになるが、今回確かめていないmsvcでは挙動が違うという噂もある。(こちらの記事にチョロッとそれっぽいことが)
MSVCでの実行結果も踏まえ再考察。やはりメモリ確保に関する規格自体は総じて緩いといえる。MSVCではキャパシティが異なったのもその一例である。しかし主要な実装(少なくともGCC, Clang, MSVC)においては「clear()
はメモリを再確保しない」という点でなら挙動は共通している。
勘違いや取りこぼしについては、補足してくれる方がいれば嬉しいです。