2017-04-13 92 views
1

举例来说,我有一个结构定义通过字节数组构造虚拟函数的结构是否安全?

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;

我问,因为我读具有虚函数的类存储 虚拟表指针并且没有标准定义它在哪里存储 在对象中编辑。另外,如果我继承的数据,例如

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

什么可以的顺序成员变量在数据2 的对象存储?如果我在一个字节数组上施加Data2结构,它会在 的顺序data1,data2,data3?先谢谢你。

+1

因为你遇到了对齐问题(和'struct Data {uint8_t data1; uint16_t data2;}'几乎肯定会有大小4,而不是3),所以这是不安全的。 – melpomene

+0

一个首选的方法是从缓冲区单独分配成员到结构中。这可以让你处理对齐和endian问题。 –

+0

对不起,忘记提及存储Allignment。即使使用分配宏作为#pragma(pack,1),可以安全地将它与结构中的虚函数一起使用吗? –

回答

4

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

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

请注意,为了检查它的表示形式,将Data *转换为uint8_t *是合法的。将这样的uint8_t*转换回Data*也是合法的。

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

+0

因为你说禁止也是合法的,所以我实际上很困惑。 –

+0

我们有一串可以来自网络或仅来自文件的字节流。目前,我们做了这样的演员并获得了数据。我的目标是简化当前流程并使其易于扩展和维护,即。如果新版本(例如具有更多成员的结构)存在或保持向后兼容性。 –

+0

我们使用编译指示包对整个结构进行排列。 –

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,其引爆不确定的行为,因为指针的数据不准别名指向uint8_t的指针。因此,在这种情况下,双投(或深拷贝)是必需的,编译器不会做出关于指针太多的假设:

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

而另一个问题可能是写在阵列uint16_t领域的不同字节,但不幸的是没有直接的方法来处理它。