写入类的最后一个字节

blu*_*rni 3 c++ c++11

给定具有标准布局成员的标准布局类,例如:

struct foo {
    int n;
    int m;
    unsigned char garbage;
};
Run Code Online (Sandbox Code Playgroud)

将它永远是安全的,按照标准,在该结构的最后一个字节写不写入的存储区域nm(但可能写入garbage)?例如,

foo f;
*(static_cast<unsigned char *>(static_cast<void *>(&f)) + (sizeof(foo) - 1u)) = 0u;
Run Code Online (Sandbox Code Playgroud)

花了一些时间阅读C++ 11标准后,在我看来答案可能是肯定的.

从9.2/15:

分配具有相同访问控制(第11条)的(非联合)类的非静态数据成员,以便后面的成员在类对象中具有更高的地址.未指定具有不同访问控制的非静态数据成员的分配顺序(11).实施对齐要求可能导致两个相邻成员不能立即分配; 因此,可能需要空间来管理虚拟功能(10.3)和虚拟基类(10.1).

因此,该garbage成员具有比其他两个成员更高的地址(由于它们具有标准布局而连续存储),并且结构的最后一个字节因此必须属于garbage或者是最终填充的一部分.

这个推理是否正确?我f在这里干涉对象的生命吗?写入填充字节是一个问题吗?

编辑

在回复评论时,我在这里想要实现的是与我正在编写的类似于类的类.

如果以直接的方式进行(即,在变量类中放置一个int成员来记录存储的类型),填充将使该类几乎比它需要的大50%.

我要做的是确保我将要存储在变体中的每个类类型的每个最后一个字节都是可写的,因此我可以将存储标志合并到我正在使用的原始存储(对齐的原始字符数组)中变种.在我的具体情况下,这消除了大部分浪费的空间.

编辑2

作为一个实际示例,请考虑将这两个类存储在典型64位计算机上的变体中:

// Small dynamic vector class storing 8-bit integers.
class first {
    std::int8_t    *m_ptr;
    unsigned short m_size_capacity; // Size and capacity packed into a single ushort.
};

// Vector class with static storage.
class second {
    std::int8_t  m_data[15];
    std::uint8_t m_size;
};

class variant
{
    char m_data[...] // Properly sized and aligned for first and second.
    bool m_flag; // Flag to signal which class is being stored.
};
Run Code Online (Sandbox Code Playgroud)

我的机器上这两个类的大小是16,变量类中需要的额外成员使得大小变为24.如果我现在添加垃圾字节:

// Small dynamic vector class storing 8-bit integers.
class first {
    std::int8_t    *m_ptr;
    unsigned short m_size_capacity; // Size and capacity packed into a single ushort.
    unsigned char  m_garbage;
};

// Vector class with static storage.
class second {
    std::int8_t    m_data[14]; // Note I lost a vector element here.
    std::uint8_t   m_size;
    unsigned char  m_garbage;
};
Run Code Online (Sandbox Code Playgroud)

这两个类的大小仍然是16,但是如果现在我可以自由地使用每个类的最后一个字节,我可以取消变体中的标志成员,最终大小仍然是16.

Ben*_*igt 9

相反,您应该首先放置标签,然后是其他小成员.

// Small dynamic vector class storing 8-bit integers.
struct first
{
    unsigned char  m_tag;
    std::uint8_t   m_size;
    std::uint8_t   m_capacity;
    std::int8_t    *m_ptr;
};

// Vector class with static storage.
struct second
{
    unsigned char  m_tag;
    std::uint8_t   m_size;
    std::int8_t    m_data[14];
};
Run Code Online (Sandbox Code Playgroud)

然后,语言规则允许您将它们放入union并使用任一个访问m_tag,即使它不是联合的"活动"成员,因为初始布局是相同的(成员的共同初始序列的特殊规则) ).

union tight_vector
{
     first dynamic;
     second small_opt;
};

tight_vector v;
if (v.dynamic.m_size < 4) throw std::exception("Not enough data");
if (v.dynamic.m_tag == DYNAMIC) { /* use v.dynamic */ }
else { /* use v.small_opt */ }
Run Code Online (Sandbox Code Playgroud)

有问题的规则是9.2/18:

如果标准布局联合包含两个或多个共享公共初始序列的标准布局结构,并且如果标准布局联合对象当前包含这些标准布局结构中的一个,则允许检查任何公共初始部分.他们 如果相应的成员具有布局兼容类型并且两个成员都不是位字段,或者两者都是具有相同宽度的位字段(对于一个或多个初始成员的序列),则两个标准布局结构共享共同的初始序列.

  • @ user1131467:对于C++功能建议来说,这是完全错误的地方.真的,我不希望自动完成(它会选择哪个名称?,规则不要求名称匹配),但是`使用m_size = v.dynamic.m_size;`会很棒. (2认同)