2010-02-21 61 views
7

我有一個672字節長的遺留數據結構。這些結構都存儲在一個文件中,按順序,我需要在閱讀它們C++結構比對和STL向量

雖然我可以用一個接一個讀他們,這將是很好的做到這一點。

// I know in advance how many structs to read in 
vector<MyStruct> bunchOfStructs; 
bunchOfStructs.resize(numberOfStructs); 

ifstream ifs; 
ifs.open("file.dat"); 
if (ifs) { 
    ifs.read(&bunchOfStructs[0], sizeof(MyStruct) * numberOfStructs); 
} 

這工作,但我認爲它只能工作,因爲數據結構大小碰巧可以被我的編譯器的結構對齊填充整除。我懷疑它會在另一個編譯器或平臺上崩潰。

另一種方法是使用for循環來一次讀取每個結構。

問題 - >何時需要關注數據對齊?在向量中動態分配內存使用填充還是STL確保元素是連續的?

+0

這些結構是通過遺留代碼寫入文件還是您控制它? – 2010-02-21 23:47:44

+0

它們由遺留代碼編寫。即使我可以更改它,我也可能需要閱讀由較早版本的應用程序編寫的文件。 – Nate 2010-02-21 23:49:57

回答

4

該標準要求您能夠創建一個結構類型的數組。當你這樣做時,數組必須是連續的。這意味着,無論爲該結構分配了多少大小,它都必須允許您創建它們的數組。爲確保編譯器能在內部分配額外的空間這個結構,但不能在這些結構之間需要任何額外的空間。

用於在vector數據的空間是(通常)與::operator new分配(經由分配器類),以及::operator new需要來分配已正確對準,以存儲任何類型的空間。

你可以提供你自己的分配器和/或過載::operator new - 但如果你這樣做,你的版本仍然需要滿足同樣的需求,所以它不會改變這方面的東西。

換句話說,只要文件中的數據的創建方式與您嘗試讀取該文件的方式基本相同,就可以正常工作。如果它是在另一臺機器上創建的,或與一個不同的編譯器(或者甚至是具有不同標誌的同一個編譯器),你有相當多的潛在問題 - 你可能會在字節順序,結構中的填充等方面有所不同。

編輯:既然你不知道是否有結構由編譯器所需要的格式被寫出來,你不僅需要一次讀取結構一個 - 你真的需要閱讀然後將每個項目放到一個臨時的struct中,最後將填充的struct添加到您的收藏中。

幸運的是,您可以重載operator>>以自動完成大部分操作。這不能提高速度(例如),但它可以讓你的代碼更加清晰:

struct whatever { 
    int x, y, z; 
    char stuff[672-3*sizeof(int)]; 

    friend std::istream &operator>>(std::istream &is, whatever &w) { 
     is >> w.x >> w.y >> w.z; 
     return is.read(w.stuff, sizeof(w.stuff); 
    } 
}; 

int main(int argc, char **argv) { 
    std::vector<whatever> data; 

    assert(argc>1); 

    std::ifstream infile(argv[1]); 

    std::copy(std::istream_iterator<whatever>(infile), 
       std::istream_iterator<whatever>(), 
       std::back_inserter(data)); 
    return 0; 
} 
+0

完美。我知道在磁盤上的結構之間沒有填充,並且在磁盤上沒有填充*結構。但是,我想我沒有可移植的方法來知道編譯器是否要在內存中的結構中添加填充。所以看起來我需要一次性閱讀一些東西才能保證安全。 – Nate 2010-02-21 23:59:30

2

對於你現有的文件,你最好的辦法是找出它的文件格式,並單獨讀取每種類型,讀入和丟棄任何對齊字節。

最好不要對結構對齊做出任何假設。

要將新數據保存到文件,可以使用類似boost serialization之類的內容。

+0

這聽起來像安全的方式。緩慢而乏味,但安全。 :-)我知道在磁盤格式中沒有填充。 – Nate 2010-02-21 23:52:37

2

就你而言,只要可能改變結構的佈局,就需要關注對齊。有兩個選項可以讓你的代碼更加便攜。首先,大多數編譯器具有擴展屬性或預處理器指令,這些指令可以讓您將結構打包到最小空間。此選項可能會錯位結構中的某些字段,這可能會降低性能,但可以保證在您構建它的任何機器上它的佈局相同。檢查您的編譯器是否有關於#pragma pack()的文檔。在GCC中,您可以使用__attribute__((__packed__))

其次,您可以添加顯式填充到您的結構。該選項允許您保持原始結構的性能屬性,但會使結構的佈局明確無誤。例如:

比對準
struct s { 
    u_int8_t field1; 
    u_int8_t pad0[3]; 
    u_int16_t field2; 
    u_int8_t pad1[2]; 
    u_int32_t field3; 
}; 
1

更多,你應該擔心endianness。 STL保證vector中的存儲與數組相同,但是結構本身中的整數字段將以不同的格式存儲在x86和RISC之間。

至於對齊的東西,谷歌爲#pragma pack(1)

0

如果你正在編寫需要一個類的內部運作的知識面向對象的代碼,你這樣做錯誤。你應該對這個階級的內在運作不予理睬。你應該只假定方法和屬性在任何平臺/編譯器上都是一樣的。

你可能會更好地實現一個模擬矢量功能的類(也許通過子類化矢量)。或許作爲一種「代理模式」實現,它可能只加載那些被調用者訪問過的結構。這將允許您同時處理任何endian問題。這種方式應該使其適用於任何平臺或編譯器。