2011-03-22 141 views
33

如果我在C++中有struct,是否沒有辦法將它安全地讀/寫到跨平臺/編譯器兼容的文件中?C++中的結構填充

因爲如果我理解正確,每個編譯器都會根據目標平臺進行不同的填充。

+3

通過執行二進制I/O獲得的效率(性能)往往不足以證明研究,設計,開發,特別是調試和維護所花費的資金。源代碼應該很容易理解,但並不簡單。 – 2011-03-22 22:24:50

回答

33

不,這是不可能的。這是因爲缺少C++在二進制級別的標準化。

Don Box

C++和可移植性


一旦決定要 由分發C++類的(從他的書Essential COM,章COM作爲一種更好的C++報價) DLL,其中一個 面臨着的一個基本問題 C++的弱點,也就是lac k的 標準化在二進制級別。 雖然ISO/ANSI C++草案 工作文件試圖編纂這 程序編譯和什麼運行它們將 是,的 語義效果也沒有試圖規範 的C++二進制運行時模型。該 第一次這個問題將成爲 明顯的是,當一個客戶端嘗試對FastString DLL的導入庫從 一個C++的研究與開發環境等一個用於構建 FastString DLL鏈接 。

結構填充由不同的編譯器完成。即使您使用相同的編譯器,根據您使用的pragma pack,結構的打包對齊方式也可能不同。

不僅如此,如果你寫兩個結構,其成員是正是一樣的,唯一的不同的是,在他們聲明的順序是不同的,那麼每個結構的大小可以(而且經常是)不同。

例如,看到這種情況,

struct A 
{ 
    char c; 
    char d; 
    int i; 
}; 

struct B 
{ 
    char c; 
    int i; 
    char d; 
}; 

int main() { 
     cout << sizeof(A) << endl; 
     cout << sizeof(B) << endl; 
} 

gcc-4.3.4編譯它,你會得到這樣的輸出:

8 
12 

也就是說,大小即使這兩個結構具有相同的成員有不同的!在Ideone

代碼:http://ideone.com/HGGVl

的底線是,標準不談論填充應該怎麼做,所以編譯器可以自由地做出任何決定,你不能承擔所有的編譯器做同樣的決定。

+3

有'__attribute __((packed ))'我用於共享內存結構以及用於映射網絡數據的結構。它確實會影響性能(請參閱http://digitalvampire.org/blog/index.php/2006/07/31/why-you-shouldnt-use-__attribute__packed/),但它對網絡相關結構非常有用。 (就我所知,這不是一個標準,所以答案依然如此)。 – Pijusn 2015-06-08 07:11:55

+0

我不明白爲什麼struct A的大小是8而不是更多。 { char c; // 那這個呢? char d; // size 1 + padding of 3 int i; // size 4 }; – Dchris 2017-03-03 08:01:28

+3

@Dchris - 編譯器可能很小心地確保每個字段基於自己的自然對齊進行對齊。 c和d是一個字節,因此無論您將它們放在單字節CPU指令的哪個位置,它們都是對齊的。然而,int需要在一個4字節的邊界上對齊,要達到那個邊界需要在d之後填充兩個字節。這讓你到8。 – hoodaticus 2017-05-25 20:58:22

2

您可以使用類似boost::serialization之類的東西。

6

不,沒有安全的方法。除了填充之外,還必須處理不同的字節排序和不同大小的內置類型。

您需要定義一種文件格式,並將結構轉換爲該格式。序列化庫(例如boost :: serialization或google的協議緩衝區)可以幫助解決這個問題。

+1

「結構(或類)的大小可能不等於其成員大小的總和。」 – 2011-03-22 22:21:04

+0

@Thomas:沒錯。這只是開始的樂趣。 – Erik 2011-03-22 22:23:05

3

長話短說,沒有。沒有平臺無關的標準符合方式來處理填充。

填充被稱爲在標準 「對準」,並且它開始在3.9/5討論它:

對象類型具有對準 要求(3.9.1,3.9.2)。完整對象類型的對齊方式爲 實現定義的整數 表示多個字節的值; 對象分配在地址 ,滿足其對象類型的對齊要求 。

但它從那裏繼續前進,並轉移到標準的許多黑暗角落。對齊是「實現定義的」,這意味着它可以在不同編譯器之間不同,甚至在編譯器的相同下的地址模型(即32位/ 64位)上不同。

除非您有嚴格的性能要求,否則您可能會考慮以不同的格式(如字符串)將數據存儲到光盤。當自然格式可能是其他內容時,許多高性能協議都會使用字符串發送所有內容。例如,我最近處理的低延遲交換饋送發送日期爲格式如下的字符串:「20110321」,時間類似地發送:「141055.200」。儘管此交換饋送整天發送500萬條消息,但它們仍然使用字符串處理所有內容,因爲這樣可以避免排序和其他問題。

11

如果你有機會自己設計結構,它應該是可能的。基本思想是你應該設計它,這樣就不需要在其中插入填充字節。第二個訣竅是你必須處理差異性。

我將介紹如何使用標量構造結構,但只要您對每個包含的結構應用相同的設計,就應該可以使用嵌套的結構。

首先,C和C++的基本事實是,類型的對齊不能超過類型的大小。如果會,那麼將不可能使用malloc(N*sizeof(the_type))分配內存。

佈局結構,從最大的類型開始。

struct 
{ 
    uint64_t alpha; 
    uint32_t beta; 
    uint32_t gamma; 
    uint8_t delta; 

接下來,墊了手動的結構,所以,在年底你將匹配最大的類型:

uint8_t pad8[3]; // Match uint32_t 
    uint32_t pad32;  // Even number of uint32_t 
} 

下一步是決定是否結構應存放在小或大endian格式。如果存儲格式與主機系統的字節不匹配,最好的方法是在寫入之前或讀取結構之後,在原地10「交換」所有元素

+0

這聽起來很有趣。但是,您能詳細瞭解一下:爲什麼按照長度遞減的順序排列它,爲什麼要填充它,使得偶數個uint32_t? – Phil 2015-02-21 22:52:15

+1

@Phil,一個基本類型,比如'uint32_t',可以(可能)有一個匹配它的大小的對齊需求,在這個例子中是4個字節。編譯器可以插入填充來實現這一點。通過手動執行此操作,編譯器無需執行此操作,因爲對齊總是正確的。缺點是在對齊要求不太嚴格的系統上,手動填充的結構將比編譯器填充的結構大。您可以按照升序或降序完成此操作,但是如果您按升序執行int,則需要在結構體的中間插入更多墊... – Lindydancer 2015-02-22 08:34:45

+1

...僅在您需要時才需要填充結構體的結尾計劃在數組中使用它。 – Lindydancer 2015-02-22 08:35:13