2010-02-21 92 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问题。这种方式应该使其适用于任何平台或编译器。