Hinnant的short_alloc和对齐保证

时间:2015-11-15 17:49:11

标签: c++ c++11 memory-management

我最近遇到了Howard Hinnant的short_alloc,这是我见过的自定义分配器的最好例子。

但是当我花更多时间研究代码以将其集成到我的个人项目中时,我发现提供基于堆栈的分配的SomeClassName::类可能并不总是返回正确对齐的内存。实际上,我担心只有第一次分配才能保证适当对齐(因为缓冲区本身具有强制对齐),请参阅下面的相关代码片段:

arena

我可以考虑一些方法来解决这个问题(以一些内存浪费为代价),最简单的方法是将template <std::size_t N> class arena { static const std::size_t alignment = 16; alignas(alignment) char buf_[N]; char* ptr_; //... }; template <std::size_t N> char* arena<N>::allocate(std::size_t n) { assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena"); if (buf_ + N - ptr_ >= n) { char* r = ptr_; ptr_ += n; return r; } return static_cast<char*>(::operator new(n)); } 函数中的size四舍五入为allocate/deallocate的倍数}。

但在改变任何事情之前,我想确保我在这里没有遗漏任何东西......

1 个答案:

答案 0 :(得分:32)

此代码是在我的工具箱中有std::max_align_t之前编写的(现在位于<cstddef>)。我现在写这个:

static const std::size_t alignment = alignof(std::max_align_t);

在我的系统上完全等同于当前的代码,但现在更加便携。这是newmalloc保证返回的对齐方式。一旦你有了这个&#34;最大程度地对齐&#34;缓冲区,你可以在其中放入任何一种类型的数组。但是,对于不同的类型,您不能使用相同的arena(至少不是具有不同对齐要求的不同类型)。因此,也许最好在第二个arena上模板size_t,这等于alignof(T)。这样,您可以防止具有不同对齐要求的类型意外使用相同的arena

arena<N, alignof(T)>& a_;

假设来自arena的每个分配具有相同的对齐要求,并且假设缓冲区是最大对齐的,那么缓冲区中的每个分配都将适当地对齐T

E.g。在我的系统alignof(std::max_align_t) == 16上。具有此对齐的缓冲区可以包含以下数组:

  • alignof == 1的类型。
  • alignof == 2的类型。
  • alignof == 4的类型。
  • alignof == 8的类型。
  • alignof == 16的类型。

由于某些环境可能支持具有&#34; super alignment&#34;要求,增加安全预防措施(例如在short_alloc内):

static_assert(alignof(T) <= alignof(std::max_align_t), "");

如果你是超级偏执者,你也可以检查alignof(T)是2的幂,尽管C ++标准本身保证这一直是真的([basic.align] / p4)。

<强>更新

我已经仔细研究了这个问题,并认为将请求的分配大小四舍五入到下一个alignment(如OP建议的那样)是最好的解决方案。我已在我的网站上更新了"short_alloc"

template <std::size_t N>
char*
arena<N>::allocate(std::size_t n)
{
    assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
    n = align_up(n);
    if (buf_ + N - ptr_ >= n)
    {
        char* r = ptr_;
        ptr_ += n;
        return r;
    }
    return static_cast<char*>(::operator new(n));
}

对于知道您不需要最大限度对齐分配的特殊情况(例如vector<unsigned char>),您可以恰当地调整alignment。也可以short_alloc::allocatealignof(T)传递给arena::allocateassert(requested_align <= alignment)

template <std::size_t N>
char*
arena<N>::allocate(std::size_t n, std::size_t requested_align)
{
    assert(requested_align <= alignment);
    assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
    n = align_up(n);
    if (buf_ + N - ptr_ >= n)
    {
        char* r = ptr_;
        ptr_ += n;
        return r;
    }
    return static_cast<char*>(::operator new(n));
}

这会让您相信,如果您向下调整alignment,则不会向下调整太远。

再次更新!

我已经更新了此分配器的descriptioncode因为这个优秀的问题(我多年来忽略了这段代码)。

上一次更新中提到的对齐检查现在在编译时完成(编译时错误总是优于运行时错误,甚至是断言)。

现在,arenashort_alloc都会在对齐时进行模板化,以便您可以轻松自定义预期的对齐要求(如果您认为在编译时捕获的对齐要求太低)。此模板参数默认为alignof(std::max_align_t)

arena::allocate功能现在看起来像是:

template <std::size_t N, std::size_t alignment>
template <std::size_t ReqAlign>
char*
arena<N, alignment>::allocate(std::size_t n)
{
    static_assert(ReqAlign <= alignment, "alignment is too small for this arena");
    assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
    auto const aligned_n = align_up(n);
    if (buf_ + N - ptr_ >= aligned_n)
    {
        char* r = ptr_;
        ptr_ += aligned_n;
        return r;
    }
    return static_cast<char*>(::operator new(n));
}

感谢别名模板,这个分配器比以往更容易使用。例如:

// Create a vector<T> template with a small buffer of 200 bytes.
//   Note for vector it is possible to reduce the alignment requirements
//   down to alignof(T) because vector doesn't allocate anything but T's.
//   And if we're wrong about that guess, it is a comple-time error, not
//   a run time error.
template <class T, std::size_t BufSize = 200>
using SmallVector = std::vector<T, short_alloc<T, BufSize, alignof(T)>>;

// Create the stack-based arena from which to allocate
SmallVector<int>::allocator_type::arena_type a;
// Create the vector which uses that arena.
SmallVector<int> v{a};

这不一定是这些分配器中的最后一个词。但希望这是一个可以构建自定义分配器的坚实基础。