初期化時に大きめのアドレス空間(またはメモリープール)を予約しておいて、毎回割り当てする時間を省略するってのがプレースメント new の目的ですが、基本ルールはスタック領域に割り当てられちゃいます。
ですが、これを捻じ曲げる原則があります。
つまりデスネ…
static で宣言したらプログラム開始から終了までがオブジェクトの有効期間(またはライフタイム)になるわけです。
でもスタック領域に割り当てたらポップしたら消えちゃうので駄目っすよね?
static で宣言した配列はヒープ領域(もしくは mmap のメモリー領域)にないと駄目ってことです。
まあ規格ではこれが書かれてないはずなので、経験則なんですどね。
では軽く検証してみましょう。
main.cpp.
1 #include <cstddef> 2 #include <new> 3 #include <cstddef> 4 #include <cstdio> 5 6 #define MAX_OBJS 1024 7 8 struct A 9 { 10 static std::byte M_data_[MAX_OBJS]; 11 }; 12 13 std::byte A::M_data_[MAX_OBJS]; 14 15 static A a; 16 17 int main() 18 { 19 ::new (A::M_data_) std::byte[MAX_OBJS]; 20 A::M_data_[0] |= std::byte(0b1111); 21 A::M_data_[1] |= std::byte(0b1010); 22 std::printf("[0] %p : %u\n",A::M_data_,(int)A::M_data_[0]); 23 std::printf("[1] %p : %u\n",&(A::M_data_[1]),(int)A::M_data_[1]); 24 a.M_data_[0] |= std::byte(0b1100); 25 std::printf("[0] %p : %u\n",a.M_data_,(int)a.M_data_[0]); 26 std::printf("[1] %p : %u\n",&(a.M_data_[1]),(int)a.M_data_[1]); 27 return 0; 28 }
ビルドと実行結果.
$ g++ main.cpp -std=c++17 $ ./a.out [0] 0x5582aba10040 : 15 [1] 0x5582aba10041 : 10 [0] 0x5582aba10040 : 15 [1] 0x5582aba10041 : 10
それでスタティックオブジェクトの宣言・定義を見てみましょうかね。
8 struct A 9 { 10 static std::byte M_data_[MAX_OBJS]; 11 }; 12 13 std::byte A::M_data_[MAX_OBJS]; 14 15 static A a;
A は何も実装されていないアロケーターだとすると M_data_ が記憶域ってことになります。
std::byte は 1 バイトなので M_data_ は MAX_OBJS バイトの記憶域になります。
[0] 0x5582aba10040 : 15 [1] 0x5582aba10041 : 10 [0] 0x5582aba10040 : 15 [1] 0x5582aba10041 : 10
メモリーアドレスを見るとヒープっぽいです。
理由は割り当て時のアドレスが 0x55… で始まるからです。
新規割り当て時にスタック領域では上位アドレスから下にいき、ヒープ領域では下位アドレスから上方向にアドレスがシフトしますんで、ヒープ領域はスタック領域より下位のアドレスからスタートします。
厳密に言うとスタック領域も配列内の移動であれば下位アドレスから上方向に行きますが、アドレスの開始点は 0x7f…. となると思います。
まあスタティックオブジェクトはヒープというのはあくまでも実装依存だと思うので、必ずそうなるとは限らないってことだと思います。
Copyright 2018-2019, by Masaki Komatsu