第95章 アロケーターをスタティックメンバーにするとどうなるのか?

アロケーターをスタティックにすると、スタティックアロケーターと呼ぶことがあります。

これって誤解・誤読が起きる原因なんです… (´・ω・`)

スタックアロケーターと名前が似て… る… ですよねぇ?

例えば以下の 3 つは必ずしも同じものではないです。

特に最後のスタックアロケーターなんて… (´・ω・`)

まあここで重要なのは static で宣言したアロケーターと static メンバーとして宣言したアロケーターです。

この 2 つは同じものと考えて良いでしょうが、一応検証しときましょう。

スタティックアロケーターの中で宣言・定義したデータは、スタック領域以外のアドレス、つまりヒープ領域に割り当てされるはずです。

なぜなら static で宣言・定義した配列は明らかにヒープ領域っぽいアドレスになっていたことは前の項目で確認したとおりだからです。

では上記 2 つの static の宣言がメモリー割り当てにどのような影響を与えるか検証していきましょう。

main.cpp. 

  1 #include <iostream>
  2 #include <cstdio>
  3
  4 struct A {
  5   int count;
  6   char* mem_;
  7   char x_[2];
  8   A() : count(0)
  9   {
 10     mem_ = new (&x_) char(sizeof(char[2]));
 11   }
 12   void fill(char x, char y)
 13   {
 14     mem_[0] = x;
 15     mem_[1] = y;
 16   }
 17   char get1(){
 18     return static_cast<char>(mem_[0]);
 19   }
 20   char get2(){
 21     return static_cast<char>(mem_[1]);
 22   }
 23 };
 24
 25 template<typename T>
 26 struct B{
 27   static A a;
 28 };
 29
 30 template<typename T>
 31 A B<T>::a;
 32
 33 template<>
 34 struct B<int>{
 35   static A a;
 36 };
 37
 38 template<>
 39 struct B<char>{
 40   static A a;
 41 };
 42
 43 A B<int>::a;
 44 A B<char>::a;
 45
 46 int main()
 47 {
 48   B<int> b1;
 49   b1.a.count = 100;
 50   b1.a.fill('a','b');
 51   B<char> b2;
 52   b2.a.count = 200;
 53   b2.a.fill('c','d');
 54   B<int> b3;
 55   b3.a.count = 1000;
 56   b3.a.fill('d','e');
 57   // B<int> と B<char> のインスタンスは別々で独立している
 58   std::cout << b1.a.count << " : " << b2.a.count << '\n';
 59   std::cout << b1.a.get1() << " : " << b2.a.get1() << '\n';
 60   std::cout << b1.a.get2() << " : " << b2.a.get2() << '\n';
 61   // b1 と b3 は同じテンプレートのインスタンスになってしまう
 62   std::cout << b1.a.get1() << " : " << b3.a.get1() << '\n';
 63   std::cout << b1.a.get2() << " : " << b3.a.get2() << '\n';
 64
 65   std::printf("B<int> b1.a     => %p\n",&(b1.a));
 66   std::printf("B<int> b1.a.mem => %p\n",b1.a.mem_);
 67   std::printf("B<char> b2.a    => %p\n",&(b2.a));
 68   std::printf("B<int> b3.a     => %p\n",&(b3.a));
 69
 70   return 0;
 71 }

ビルドと実行結果. 

$ g++ main.cpp
$ ./a.out
1000 : 200
d : c
e : d
d : d
e : e
B<int> b1.a     => 0x56481aa74140
B<int> b1.a.mem => 0x56481aa74150
B<char> b2.a    => 0x56481aa74160
B<int> b3.a     => 0x56481aa74140

ではアロケーターをスタティックメンバーにしてみた箇所です。

 25 template<typename T>
 26 struct B{
 27   static A a;
 28 };
 29
 30 template<typename T>
 31 A B<T>::a;
 32
 33 template<>
 34 struct B<int>{
 35   static A a;
 36 };
 37
 38 template<>
 39 struct B<char>{
 40   static A a;
 41 };
 42
 43 A B<int>::a;
 44 A B<char>::a;

クラステンプレート B の中にアロケータークラス A のインスタンス a を static で宣言・定義していますね。

それとテンプレート特殊化で int と char を T に対して設定したインスタンスも定義していますね。

アロケータークラス A は以下のように mem_ をメモリープールにします。

  4 struct A {
  5   int count;
  6   char* mem_;
  7   char x_[2];
  8   A() : count(0)
  9   {
 10     mem_ = new (&x_) char(sizeof(char[2]));
 11   }

A は何もしなければスタックアロケーターと考えることができます。

てことはアドレスはスタック領域に割り当てられそうですが、以下の行でその希望は打ち砕かれます。

 65   std::printf("B<int> b1.a     => %p\n",&(b1.a));
 66   std::printf("B<int> b1.a.mem => %p\n",b1.a.mem_);
 67   std::printf("B<char> b2.a    => %p\n",&(b2.a));
 68   std::printf("B<int> b3.a     => %p\n",&(b3.a));

 これを実行して出力させたものが以下です。

B<int> b1.a     => 0x56481aa74140
B<int> b1.a.mem => 0x56481aa74150
B<char> b2.a    => 0x56481aa74160
B<int> b3.a     => 0x56481aa74140

割り当てられたアドレスは 0x5… で開始しており、ヒープらしき領域のアドレスと確認ができます。

さらに b1.a と b3.a は同じアドレスになっています。

これはテンプレート引数が同じ int の B の中のインスタンス a は static で宣言されているからです。

Copyright 2018-2019, by Masaki Komatsu