【发布时间】: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