それではヒープ領域に割り当てる方式を見て見ましょう。
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