在字节数组上构建具有虚函数的结构是否安全?

时间:2017-04-13 19:04:26

标签: c++ arrays memory struct

例如,我有一个结构定义

struct Data { 
    uint8_t data1; 
    uint16_t data2;
    virtual uint8_t getData1() { return data1; }
    virtual uint16_t getData2() { return data2; } 
}

我有一个字节数组

uint8_t data[3];

这样做是否安全:

Data *d = (Data*)data;

我问,因为我读了一个带有虚函数的类存储 虚拟表指针,并没有标准定义它 存储在对象中。另外,如果我从Data继承,例如

struct Data2 : Data { uint8_t data3; 
virtual uint8_t getData3() { return data3; } }

Data2对象中成员变量的顺序是什么 存储?如果我在一个字节数组上转换Data2结构,它将在 订单data1,data2,data3?提前谢谢。

2 个答案:

答案 0 :(得分:4)

在c ++中,c style cast被解释为一个等效的c ++强制转换,最具限制性的仍然可以完成转换。在这种情况下,它是reinterpret_cast

reinterpret_cast上的文档列举了每个已定义的用例。不幸的是,您的情况被禁止取消引用结果指针。虚拟方法的存在与此无关。

请注意,执行相反操作并将Data *强制转换为uint8_t *以检查其表示形式是合法的。将此类uint8_t*投回Data*也是合法的。

修改:如果您的目标是为Data的实例提供存储空间,则可以使用std::aligned_storageplacement newstd::aligned_storage提供了一个安全的内存位置,可以在其中构造一个类型的实例,并且placement new允许您指定构造实例的位置。但是,如果您打算存储派生类型,这将无法正常工作。

答案 1 :(得分:1)

通常,当某些数据结构的字节数组出现时,开发人员有责任确保此结构的二进制布局。在您的示例中,二进制布局可能会有很大差异,而vtable的存在只是其中一个问题。另一个问题是字段的对齐。它通常取决于编译选项。例如,如果对齐是4个字节,那么结构的一侧将是至少8个字节+与vtable相关的指针,这显然不适合3个字节的数组。因此,在这种情况下执行演员表和/或深拷贝将导致严重的麻烦。

要确保结构大小正确,您可以使用#pragma pack或类似的构造和静态断言,如下所示:

#pragma pack(push, 1) // make sure that fields are packed

struct Data { 
uint8_t data1; 
uint16_t data2;
};

#pragma pack(pop) // restore initial alignment settings

static_assert(sizeof(Data) == 3, "Data struct layout is not correct");

另一个问题是strict aliasing rules,它会引爆未定义的行为,因为不允许指向Data的指针为指向uint8_t的指针设置别名。所以在这种情况下需要双重转换(或深拷贝),因此编译器不会对指针做太多假设:

Data *d = reinterpret_cast< Data * >(reinterpret_cast< ::std::uintptr_t >(data));

另一个问题可能是用数组写的uint16_t字段的不同字节序,但不幸的是没有直接的方法来处理它。