問題
VisualStudio2015でstd::allocate_sharedを使用した時に、型のアライメント要求が指定より小さくなる問題が発生した。型のアライメント要求は16であった。
原因
ライブラリ内部で定義されるstd::aligned_union<1, T>のアライメント要求が、Tのアライメント要求と異なるのが原因であった。
解決方法
この問題を、std::allocatorをカスタマイズしてrebind前の型の情報を保存しておき、最もアライメント要求の大きい数値を選択する事で回避した。
以下は、確認用のコードである。
rebindされた時にTを可変引数テンプレートに保存している。
#include <memory>
#include <iostream>
template <class... Types>
void print()
{
struct info
{
char const* name;
size_t size;
size_t align;
};
for (auto&& it : { info{typeid(Types).name(), sizeof(Types), alignof(Types)}... }) {
std::cout << it.name << " size=" << it.size << " align=" << it.align << std::endl;
}
}
template <class T, class... Binds>
struct my_allocator : std::allocator<T>
{
template <class U>
struct rebind
{
using other = my_allocator<U, Binds..., T>;
};
my_allocator() = default;
template <class U, class... Any>
my_allocator(my_allocator<U, Any...> const&) {}
template <class... Args>
auto allocate(Args&&... args) {
print<Binds..., T>();
return std::allocator<T>::allocate(std::forward<Args>(args)...);
}
};
int main() {
struct alignas(16) X {};
auto x = std::allocate_shared<X>(my_allocator<X>());
}
確認
上記コードをclangでコンパイルして実行すると以下の出力を得る
Z4mainE1X size=16 align=16
NSt3__120__shared_ptr_emplaceIZ4mainE1X12my_allocatorIS1_JEEEE size=48 align=16
Xのアライメント要求は16で、rebind後のTのアライメント要求も16である事が判る。
一方、VisualStudio2015でコンパイルして実行すると以下の出力を得る
struct `int __cdecl main(void)'::`2'::X size=16 align=16
class std::_Ref_count_obj_alloc<struct `int __cdecl main(void)'::`2'::X,struct my_allocator<struct `int __cdecl main(void)'::`2'::X> > size=32 align=8
VisualStudio2015では、Xのアライメント要求16に対して、rebind後のTのアラインメント要求は8である事が判る。よってTのアライメント要求でストレージを確保すると、オブジェクトが期待したアドレスに配置されない場合がある。
なお、VisualStudio2017でも同様の出力である。
struct `int __cdecl main(void)'::`2'::X size=16 align=16
class std::_Ref_count_obj_alloc<struct `int __cdecl main(void)'::`2'::X,struct my_allocator<struct `int __cdecl main(void)'::`2'::X> > size=32 align=8
結論
VisualStudioでstd::allocate_sharedを使用する場合はアライメントに注意した方が良い。