写入类的最后一个字节

时间:2013-09-02 01:15:00

标签: c++ c++11

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

struct foo {
    int n;
    int m;
    unsigned char garbage;
};
根据标准,

总是安全的,在结构的最后一个字节中写入而不写入nm的内存区域(但可能写入garbage })?如,

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

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

从9.2 / 15:

  

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

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

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

修改

在回复评论时,我在这里想要实现的目标与我正在编写的类似于类的类有关。

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

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

编辑2

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

f

我的机器上这两个类的大小是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.
};

// 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.
};

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

3 个答案:

答案 0 :(得分: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];
};

然后,语言规则允许您将这些内容放入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 */ }

有问题的规则是9.2 / 18:

  

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

答案 1 :(得分:1)

是的,在C ++中,任何对象(包括类)都表示为一系列可寻址的char对象(“bytes”),并且在没有插入访问说明符的情况下按顺序声明的对象具有顺序升序的地址。因此,garbage的存储空间必须具有比char *n更高的地址(当处理为m时)。

理论上,编译器可以在对象的末尾存储基类,或者类似于vtable指针的东西,但实际上为了简单起见,这些事情总是在开头。我不确定标准布局类的大小是什么标准保证,这与是否可以添加填充有关,这可能会影响实现是否可以依赖于某些目的的填充,但它可能归结为允许使用填充的实现,但它不能添加任何填充(并且访问可能不是简单或有效的。)

  

我要做的是确保我要在变体中存储的每个类类型的每个最后一个字节都是可写的

这与使用garbage成员本身有什么不同?如果你知道它在那里,大概你可以简单地访问它。

答案 2 :(得分:0)

结构的最后一个字节不是n和m的一部分,但是你怎么知道编译器没有在最后一个字节中存储其他东西,比如类型信息?

我不记得保证任何此类事情的标准。只有sizeof(T)的memcpy进出才会产生相同的值,这并不意味着最后一个字节不能保存信息。