【问题标题】:Allocating memory for multiple buffers including alignment为多个缓冲区分配内存,包括对齐
【发布时间】:2020-09-29 21:32:27
【问题描述】:

我想为几种不同类型的连续数组分配内存。也就是说,像这样,但具有动态大小:

template <typename T0, std::size_t N0, typename T1, std::size_t N1, ...>
struct {
    T0 t0s[N0];
    T1 t1s[N1];
    ...
};

天真地,大小只是sizeof(T0) * N0 + sizeof(T1) * N1 + ...,但我认为对齐让它变得很棘手。您将在alignof(T0) 上对齐整个内容,然后在N0 T0s 之后,您必须找出alignof(T1) 点才能启动T1s。这有std::align,但它是为了在你已经分配了内存之后找到放置它们的位置,所以在分配内存之前使用它需要一点努力。

有没有一些简单的现成方法来计算像这样的动态结构的大小和偏移量?我正在描绘一个类似的函数

// Return the offsets in bytes of the ends of each array
template <typename... Ts>
std::array<std::size_t, sizeof...(Ts)> 
calculateOffsets(const std::array<std::size_t, sizeof...(Ts)> sizes);

例如:

auto offsets = calculateOffets<char, float>({3, 2});
// Offsets is now {4, 12} because the layout is [cccXffffFFFF] where c is a char, X is padding, f and F are the two floats.
// Now we can allocate a buffer of size offsets.back() with alignment alignof(char) and placement-new 3 chars starting at buf + 0, and 2 floats at buf + offsets.front()

这很有趣,因为编译器显然内置了这个逻辑,因为在编译时它知道上面结构的布局,而大小是静态的。

(到目前为止,我只对 POD 类型感兴趣;要完全通用,我还需要处理异常安全的构造和销毁。)

【问题讨论】:

  • std::offsetof 但我不确定您将如何使用它,因为您需要知道所有班级成员的姓名。我想你可以切换到拥有一个std::tuple 数组而不是单个成员数组,以使这更容易。
  • 如果您的数组在大小上是“动态的”,那么更现实的说明是该结构将包含指向每个连续序列的多个指针吗?如果是这样,您总是可以分配所有类型的最高对齐,然后通过减少对齐大小来分配指针。这将利用在 16 字节边界对齐的对象也以 8 字节边界对齐的事实,等等
  • 您没有提供该问题的背景信息 - 因此,就我们所知,这可能是一个 X/Y 问题 - 您真正想解决什么问题?您可以在编译时使用模板等构建各种表格,然后使用循环从那里推断对齐方式,这只是一个想法。或者您可以切换设计并使用适合目标机器的通用对齐方式 - 然后手动将您的东西对齐到一些未对齐的内存上。
  • 同样,仅仅因为编译器已经构建了这个逻辑,并不意味着它应该被公开,甚至不意味着这样做很简单。
  • 我没有需要解决的特定问题,我只是惊讶地发现编译器可以轻松完成的事情似乎没有库相等的。我希望避免两个分配但有连续的内存(即,我可以分配一个 std::pair&lt;T0, T1&gt;s 的缓冲区),但随后迭代 T0s 并非易事。

标签: c++ alignment


【解决方案1】:

有没有一些现成的简单方法来计算像这样的动态结构的大小和偏移量?

标准库中没有这样的功能。您可能可以在循环中使用std::align 来实现它。

【讨论】:

  • 这是我的想法。我想我可以在std::align 的基础上编写uintptr_t intAlign(size_t alignment, size_t size, uintptr_t&amp; offset) 并使用无限的space 并以alignof(T0)offset 开头,然后从结果中减去alignof(T0)
  • 我们开始吧:godbolt.org/z/1TEYar 实际上构造和破坏左边是一个练习。将它的大小设置为从 0 开始的 n+1 可能更干净,因此前 n 个偏移量是起始偏移量。 :-)
【解决方案2】:

没有用于此的内置工具 -- 但这是 C++ 中可以解决的问题。

由于您实际上要寻找的是对齐的连续组件的“打包缓冲区”,因此我建议您通过在最对齐对象的对齐处分配所有缓冲区的总和来做到这一点。这使得以字节为单位计算缓冲区长度变得容易——但需要重新映射数据的顺序。

对齐时要记住的是,16 字节边界也与 8 字节边界对齐(等等)。另一方面,从 1 字节边界开始的 4 个字节不会在 4 字节边界上1可能是,但这取决于初始指针。

示例

例如,我们希望缓冲区中有以下内容:

  • 3 chars (c)
  • 2 floats (f)
  • 2 shorts (s)

我们假设以下情况为真:

  • alignof(float) &gt;= alignof(short) &gt;= alignof(char)
  • sizeof(float)4
  • sizeof(short)2
  • sizeof(char)1

根据我们的需要,我们需要一个 sizeof(float) * 2 + sizeof(short) * 2 + sizeof(char) * 3 = 15 字节的缓冲区。

按对齐方式排序的缓冲区在打包时如下所示:

ffff ffff ssss ccc

只要初始分配与alignof(float) 对齐,此缓冲区中的其余字节也保证正确对齐。

如果您可以将数据始终按降序排列,那将是理想的;但如果不能保证是这种情况,您始终可以使用预装模板元编程解决方案根据每个对象的 alignof(T)T... 类型列表进行排序。


1 您建议按缓冲区顺序对齐的问题是,alignof(char)(即1 字节)之类的情况并不能保证3 chars稍后,来自初始对齐的1 填充字节与4 字节对齐对齐。 0x00FFFFFE01 的指针是“1 字节对齐的”,但 4 字节之后是 0x00FFFFFE05——它仍然是未对齐的。

虽然这可能适用于某些底层分配器,例如 new/std::malloc,它们的默认对齐方式为 alignof(std::max_align_t)——但对于更精确的分配机制,例如 std::polymorphic_allocator 中的分配机制,情况并非如此。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-30
    相关资源
    最近更新 更多