【问题标题】:How to properly pack this data structure如何正确打包此数据结构
【发布时间】:2021-06-28 19:10:07
【问题描述】:

我正在尝试为 std::vector 创建一个自定义替换,我知道在绝大多数情况下,大小将

我在带有 c++17 的 Linux 和 Windows(gcc 和 msvc)上使用 64 位编译器。 int 已知是 32 位的。如果我们需要支持不同的东西,我会重新审视代码。

我基本上喜欢这种内存结构:

class SmallIntVector
{
    int m_size;
    union
    {
        int m_data[3]; // Used when m_size <=3
        int* m_dataDynamic; // Used when m_size >=4
    };
};

但是,由于对齐,它的大小为 24。我可以这样做:

class SmallIntVector
{
    int m_size;
    int m_data0; // first data member for m_size <= 3.  Return &m_data0 and treat it as data[3].
    union
    {
        int m_data12[2]; // next two data members.
        int* m_dataDynamic; // Used with m_size>=4
    };
};

但是,由于内存混叠,我很确定这是 UB。我也可以这样做:

struct ForStatic
{
    int m_size;
    int m_data[3];
};

struct ForDynamic
{
    int m_unused; // Don't use this to avoid punning.
    int *m_dataDynamic; // Pointer, must be at an 8 byte offset
};
class SmallIntVector
{
    union
    {
        ForStatic m_stat; // When m_stat.m_size is <=3, use m_stat.m_data.
        ForDynamic m_dyn; // When m_stat.m_size is >=4, use m_dyn.m_data.
    };
};

大小为 16,但我认为这是对联合的非法双关语:我将使用 ForStatic 中的 m_size,同时使用 ForDynamic 中的 m_dataDynamic。

我可以通过使用 std::memcpy 读取动态指针来轻松避免 UB,但这些都不是正确的。有什么方法可以得到我正在寻找的对齐方式并避免 UB?

我已经省略了所有的成员函数——我认为一旦结构正确,它们就很明显了。

【问题讨论】:

  • 我个人会创建自己的自定义分配器并将其与std::vector 一起使用。分配器可以在其中(作为类成员)为 3 个整数提供空间,并使用它直到请求第四个整数,然后它只为 32 个整数抓取一个块,这样它就不必再次分配。
  • @JDługosz 谢谢,我不知道 small_vector。我猜这很适合。但是,sizeof(boost::container::small_vector) 是 32。这部分是因为 boost 使用 size_t 作为大小,在我的情况下我可以使用 int,但这并不能完全解释差异.
  • 使用memcpy 实际上优化了常规访问,所以只需使用私有成员函数对其进行读/写访问,不用担心。在一个地方,您可以轻松地使用特定于编译器的内容或bit_cast(如果可用)进行更新,并确保它确实生成了最佳代码。
  • @Rob,你至少可以看看 Boost 是如何进行变体和高效打包的。

标签: c++ memory-alignment strict-aliasing


【解决方案1】:

我建议如下:

struct ForStatic
{
    int m_size;               //this must be first - overlapped in union
    int m_data[3];
};

struct ForDynamic
{
    int m_size;                //this must be first - overlapped in union
    int *m_dataDynamic;
};

class SmallIntVector
{
    union
    {
        max_align_t dummy;   // ensures alignment
        int m_size;
        ForStatic m_stat; // When m_size is <=3, use m_stat.m_data.
        ForDynamic m_dyn; // When m_size is >=4, use m_dyn.m_data.
    };
};

【讨论】:

  • 不应该m_size不在union中吗?
  • m_size, m_stat.m_size, m_dyn.m_size 都将占用相同的空间。它在语义上更容易理解。
  • @Den-Jason 感谢您的回复。这可能会使事情更清楚,但我认为它有同样的潜在问题。我认为在使用 SmallIntVector::m_size 的同时使用 SmallIntVector::m_dyn.m_dataDynamic 是非法的,因为它会影响联合。除非您使用 memcpy,否则您不能合法地写入一个成员并以不同的类型读回相同的值。我不知道这是否违反了该规则。
  • @Den-Jason 虽然这可能是真的,但如果您已经在使用其他成员之一,使用 union::m_size 将是未定义的行为
  • @NathanOliver std::variant 会是更好的解决方案吗?看着stackoverflow.com/questions/42082328/…stackoverflow.com/questions/25664848/unions-and-type-punning
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多