2017-04-13 88 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領域的不同字節,但不幸的是沒有直接的方法來處理它。