【问题标题】:std::align and std::aligned_storage for aligned allocation of memory blocksstd::align 和 std::aligned_storage 用于内存块的对齐分配
【发布时间】:2013-06-29 08:52:24
【问题描述】:

我正在尝试分配一个大小为size 的内存块,该内存块需要与Alignment 对齐,在编译时可能未定义大小。我知道存在诸如_aligned_allocposix_memalign_mm_alloc 等例程,但我不想使用它们,因为它们降低了代码的可移植性。
C++11 提供了一个例程 std::align 和一个类 std::aligned_storage,我可以从中检索 POD 类型来分配一个符合我要求的元素。然而,我的目标是创建一个分配器,它将分配一块 size 大小的内存块(不仅仅是一个元素),这将是对齐的。
这可以使用std::align 吗?我问的原因是因为std::align 移动了指针,使用该指针的类会给分配器一个指向移动地址的指针以进行释放,这将是无效的。有没有办法以这种方式创建对齐分配器?

【问题讨论】:

  • 请注意,std::align 并不是特别便携……GCC 还没有实现它。
  • @jogojapan:不,我不这么认为。这个问题特别关注 std::align 和 std::aligned_storage,而不是其他解决方案。
  • @Potatoswatter:是的,我确实注意到了!

标签: c++ c++11 alignment allocator


【解决方案1】:

编辑:在 OP 澄清后,原来的答案似乎离题了;为了参考起见,它保留在此答案的末尾。

实际上,答案相当简单:您只需要保留一个指向存储块和第一项的指针。

实际上,这并不需要有状态的分配器(即使在 C++03 中也可以,尽管使用自定义的std::align 例程)。诀窍是分配器不需要只要求系统有足够的内存来存储用户数据。它完全可以为自己的簿记目的提出更多要求。

所以,我们在这里创建一个对齐的分配器;为简单起见,我将重点介绍分配/解除分配例程。

template <typename T>
class aligned_allocator {
    // Allocates block of memory:
    // - (opt) padding
    // - offset: ptrdiff_t
    // - T * n: T
    // - (opt) padding
public:
    typedef T* pointer;
    typedef size_t size_type;

    pointer allocate(size_type n);
    void deallocate(pointer p, size_type n);

}; // class aligned_allocator

现在是分配例程。大量内存摆弄,毕竟是分配器的核心!

template <typename T>
auto aligned_allocator<T>::allocate(size_type n) -> pointer {
    size_type const alignment = std::max(alignof(ptrdiff_t), alignof(T));
    size_type const object_size = sizeof(ptrdiff_t) + sizeof(T)*n;
    size_type const buffer_size = object_size + alignment;

    // block is correctly aligned for `ptrdiff_t` because `std::malloc` returns
    // memory correctly aligned for all built-ins types.
    void* const block = std::malloc(buffer_size);

    if (block == nullptr) { throw std::bad_alloc{}; }

    // find the start of the body by suitably aligning memory,
    // note that we reserve sufficient space for the header beforehand
    void* storage = reinterpret_cast<char*>(block) + sizeof(ptrdiff_t);
    size_t shift = buffer_size;

    void* const body = std::align(alignment, object_size, storage, shift);

    // reverse track to find where the offset field starts
    char* const offset = reinterpret_cast<char*>(body) - sizeof(ptrdiff_t);

    // store the value of the offset (ie, the result of body - block)
    *reinterpret_cast<ptrdiff_t*>(offset) = sizeof(ptrdiff_t) + shift;

    // finally return the start of the body
    return reinterpret_cast<ptrdiff_t>(body);
} // aligned_allocator<T>::allocate

幸运的是,释放程序要简单得多,它只需要读取偏移量并应用它。

template <typename T>
void aligned_allocator<T>::deallocate(pointer p, size_type) {
    // find the offset field
    char const* header = reinterpret_cast<char*>(p) - sizeof(ptrdiff_t);

    // read its value
    ptrdiff_t const offset = *reinterpret_cast<ptrdiff_t*>(header);

    // apply it to find start of block
    void* const block = reinterpret_cast<char*>(p) - offset;

    // finally deallocate
    std::free(block);
} // aligned_allocator<T>::deallocate

其他例程不需要知道内存布局,因此编写它们很简单。


原答案:

template <typename T>
class Block {
public:
    Block(Block const&) = delete;
    Block& operator=(Block const&) = delete;

    explicit Block(size_t n);
    ~Block();

private:
    void* _storage;
    T* _begin;
    T* _end;
}; // class Block

template <typename T>
Block<T>::Block(size_t n) {
    size_t const object_size = n * sizeof(T);
    size_t const buffer_size = object_size + alignof(T);

    _storage = std::malloc(size);

    void* stock = _storage;
    size_t shift = buffer_size;
    std::align(alignof(T), object_size, stock, shift);

    _begin = _end = reinterpret_cast<T*>(stock);
} // Block<T>::Block

template <typename T>
Block<T>::~Block() {
    for (; _end != _begin; --_end) {
        (_end - 1)->~T();
    }

    std::free(_storage);
} // Block<T>::~Block

【讨论】:

  • Block&lt;T&gt;::Block(size_t) 中,您将相同的值分配给_begin_end_end 不应该类似于 _begin + dummy 吗?
  • 也许我在这个问题上沟通有误。要求是创建一个符合 C++ 标准分配器要求的分配器。我看到它的方式,通过存储两个指针使其成为有状态分配器,可以像你所说的那样工作。问题围绕着创建具有此类要求的分配器。
  • @TomKnapen:这是故意的,注意我没有在里面初始化任何T,我只是保留底层存储空间。
  • @intull:我确实不明白,我写了对齐分配器的核心。请注意,我假设对齐可以源自 T 的类型。
  • 是的!您在编辑中提到的技巧正是我想要的!伟大的!对此,我真的非常感激!谢谢。
【解决方案2】:

如果它必须是 C++11 解决方案,则忽略此答案。

如果不...我不知道你是否已经知道,但这里有一个选择:

void * aligned_malloc( size_t size, size_t alignement )
{
    void * p = malloc( size + --alignement );
    void * p1 = (void*)( ( (size_t)p + alignement ) & ~alignement );

    ((char*)p1)[ -1 ] = (char)((char*)p1 - (char*)p);

    return p1;
}

void aligned_free( void * pMem )
{
    char * pDelete = (char*)pMem - ((char*)pMem)[ -1 ];
    free( pDelete );
}

也许 mallocfree 不是 100% 可移植的,但使用预处理器指令很容易处理这种情况。

【讨论】:

    猜你喜欢
    • 2012-10-08
    • 2013-09-09
    • 2013-10-18
    • 2015-08-31
    • 2016-02-12
    • 2020-05-26
    • 2018-03-09
    • 1970-01-01
    相关资源
    最近更新 更多