ヒープ・動的メモリーの割り当てにはアラインメントの問題があります。
筆者の経験だとスタック領域のほうがアラインメントが問題となりがちです。
これはヒープはデフォルトで 16 バイト境界にアラインされてしまうからです。
余程特殊なハードウェアやキャッシュ最適化でも考えない限りはヒープでは見落とされがちですね。はい…
(´・ω・`)
まあ C プログラマーなら posix_memalign() / aligned_alloc() を抑えておけば十分かと思います。
posix_memalign() は C 言語の関数ではありますが C++ で使われることもあります。
理由としては C++11 に導入された alignas はスタックなら問題なかったんですがヒープでは必ずしもうまく動かないという現象があったらしいからです。
具体的にはコンパイラによっては alignas キーワードが new に適用されないという問題です。
#include <stdlib.h> int posix_memalign(void **memptr, size_t alignment, size_t size);
size 引数は割り当てサイズを指定します。
posix_memalign の割り当てたメモリーのアドレスは *memptr に設定されます。
alignment 引数には 2 の累乗数または 「 sizeof(void *) 」の倍数である必要があります。
後は類似品となる C11 標準規格の aligned_alloc ですね。
#include <stdlib.h> void *aligned_alloc(size_t alignment, size_t size);
この 2 つの関数がやることはどちらも同じです。
アラインメントのとれたヒープ領域を割り当てることです。
ではやってみましょう。
main.c.
1 #include <stdlib.h> 2 #include <stdio.h> 3 4 int main() 5 { 6 void *ptr; 7 8 printf("0x%lx\n",(unsigned long)&ptr); 9 int ret = posix_memalign(&ptr,16,sizeof(int)); 10 if(ret != 0){ 11 perror("posix_memalign"); 12 exit(1); 13 } 14 printf("0x%lx\n",(unsigned long)ptr); 15 16 free(ptr); 17 return 0; 18 }
ビルドと実行結果.
$ gcc main.c $ ./a.out 0x7ffd362ca8d0 0x562945727670
このコードは単に 16 バイト境界でヒープ領域のアドレス割り当てをしてるだけです。
アラインメントとメモリー割り当てをやってる箇所は以下の部分ですね。
9 int ret = posix_memalign(&ptr,16,sizeof(int)); 10 if(ret != 0){ 11 perror("posix_memalign"); 12 exit(1); 13 }
alignment 引数は 16 バイト、 size 引数は sizeof(int) になっていますね。
それと posix_memalign() は割り当てに成功すると 0 を返すので ret が 0 以外の時は失敗したものとみなせます。
まあ posix_memalign() は後でもっと説明していくので深入りする前に aligned_alloc() の簡単な実装をやっつけちゃいましょう。
1 #include <stdlib.h> 2 #include <stdio.h> 3 4 int main() 5 { 6 void *ptr; 7 ptr = aligned_alloc(8,sizeof(int)); 8 printf("0x%lx\n",(unsigned long)ptr); 9 free(ptr); 10 return 0; 11 }
ビルドと実行結果.
$ gcc main.c $ ./a.out 0x562fc1b6a260
aligned_alloc() をコールしてるのは以下の箇所です。
7 ptr = aligned_alloc(8,sizeof(int));
alignment 引数は 8 で、 size 引数は sizeof(int) で前の項目とは違い 8 バイト境界に設定しようとしています。
でも、実際は筆者の環境では 16 バイト境界としてアラインされてしまいます。
少なくとも執筆時の glibc の実装では aligned_alloc も posix_memalign でも結果は同じ 16 バイト境界になります。
ではなんでこうなるのか、少しだけ解明したいので、次の項目で軽く glibc のソースを読んで行きたいと思います。
Copyright 2018-2019, by Masaki Komatsu