第92章 malloc を使ってヒープに割り当て

 それではヒープ領域に割り当てる方式を見て見ましょう。

main.cpp. 

  1 #include <cstdlib>
  2 #include <cstdint>
  3 #include <new>
  4 #include <iostream>
  5
  6 int main()
  7 {
  8   char* y = (char*)std::malloc(sizeof(char)*1024);
  9   ::new (y) int(100);
 10   char* next = y + sizeof(std::uintptr_t);
 11   ::new (next) int(200);
 12   char* nnext = y + sizeof(std::uintptr_t)*2;
 13   ::new (nnext) int(300);
 14   std::free(y);
 15   return 0;
 16 }

 基本的な考え方としては malloc コールで割り当てたアドレスプール(またはアドレス領域)を再利用するってことです。

  8   char* y = (char*)std::malloc(sizeof(char)*1024);
  9   ::new (y) int(100);

 1024 バイトのヒープ領域を確保して開始アドレスに整数データ 100 を設定してるだけですね。

 次は開始アドレス以外でも割り当てが可能な試してみます。

 10   char* next = y + sizeof(std::uintptr_t);
 11   ::new (next) int(200);
 12   char* nnext = y + sizeof(std::uintptr_t)*2;
 13   ::new (nnext) int(300);

 std::uintptr_t はポインターのデータ型のことです。

 なぜ int のサイズでなくポインター変数のサイズを使うかというと、next や nnext は malloc が割り当てたのと同じアラインメントで配置 new されるのが自然だからです。

 それで malloc のバイト境界って具体的にいくらなのさ?

 てな疑問が浮かぶでしょうが、これは状況によりますね。

 筆者の知る限りでは malloc のアラインメントは実装依存となるはずでして、最低でもポインターのサイズにしておくのが安全です。

 Windows 64 bit だと 16 バイト境界、Linux だと 8 バイト境界か 16 バイト境界のいずれかになるでしょうね。

 この場合 int 型に対してなのですからポインター変数のサイズで問題ないでしょう(たぶん…)。

 ちなみにプログラムをビルドしてデバッグをすると以下のようになります。

ビルドとGDBデバッグ. 

$ g++ main.cpp -g
$ gdb ./a.out
Breakpoint 1, main () at main.cpp:8
8         char* y = (char*)std::malloc(sizeof(char)*1024);
(gdb) n
9         ::new (y) int(100);
(gdb) n
10        char* next = y + sizeof(std::uintptr_t);
(gdb) n
11        ::new (next) int(200);
(gdb) n
12        char* nnext = y + sizeof(std::uintptr_t)*2;
(gdb) n
13        ::new (nnext) int(300);
(gdb) n
14        std::free(y);
(gdb) x /u y
0x555555767e70: 100
(gdb) x /u next
0x555555767e78: 200
(gdb) x /u nnext
0x555555767e80: 300
(gdb) info proc mappings
process 7764
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
      0x555555554000     0x555555555000     0x1000        0x0
      0x555555756000     0x555555777000    0x21000        0x0 [heap]
      0x7ffff7ff7000     0x7ffff7ffa000     0x3000        0x0 [vvar]
      0x7ffff7ffa000     0x7ffff7ffc000     0x2000        0x0 [vdso]
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]

 このデバッグ結果の中で重要なのは y / next / nnext のアドレスがヒープ領域に存在することです。

 「info proc mappings」によるとヒープ領域は 0x555555756000 と 0x555555777000 の間となります。

 そして以下のように各アドレスはこの領域におさまります。

(gdb) x /u y
0x555555767e70: 100
(gdb) x /u next
0x555555767e78: 200
(gdb) x /u nnext
0x555555767e80: 300

 y が保持するアドレスは「0x555555767e70」で、ポインターの先の値は 100 となります。

 このように一旦割り当てたヒープ内でのプールでオブジェクトの構成と破壊をすることで毎回 malloc() を呼ぶことを避けることができます。

Copyright 2018-2019, by Masaki Komatsu