代码之家  ›  专栏  ›  技术社区  ›  rigon

将二进制数据(从文件)读取到结构中

  •  12
  • rigon  · 技术社区  · 14 年前

    我正在从一个文件中读取二进制数据,特别是从一个zip文件。(要了解有关zip格式结构的更多信息,请参见 http://en.wikipedia.org/wiki/ZIP_%28file_format%29 )

    我创建了一个存储数据的结构:

    typedef struct {
                                                /*Start Size            Description                                 */
        int signatute;                          /*   0  4   Local file header signature = 0x04034b50                */
        short int version;                      /*   4  2   Version needed to extract (minimum)                     */
        short int bit_flag;                     /*   6  2   General purpose bit flag                                */
        short int compression_method;           /*   8  2   Compression method                                      */
        short int time;                         /*  10  2   File last modification time                             */
        short int date;                         /*  12  2   File last modification date                             */
        int crc;                                /*  14  4   CRC-32                                                  */
        int compressed_size;                    /*  18  4   Compressed size                                         */
        int uncompressed_size;                  /*  22  4   Uncompressed size                                       */
        short int name_length;                  /*  26  2   File name length (n)                                    */
        short int extra_field_length;           /*  28  2   Extra field length (m)                                  */
        char *name;                             /*  30  n   File name                                               */
        char *extra_field;                      /*30+n  m   Extra field                                             */
    
    } ZIP_local_file_header;
    

    返回的大小 sizeof(ZIP_local_file_header) 是40,但如果每个字段的和是用 sizeof 操作员,总尺寸为38。

    如果我们有下一个结构:

    typedef struct {
        short int x;
        int y;
    } FOO;
    

    sizeof(FOO) 返回8,因为内存每次分配4个字节。所以,要分配 x 保留4个字节(实际大小为2个字节)。如果我们需要另一个 short int 它将填充前一个分配的剩余2个字节。但我们有一个 int 它将被分配4个字节,空的2个字节将被浪费。

    要从文件中读取数据,我们可以使用函数 fread :

    ZIP_local_file_header p;
    fread(&p,sizeof(ZIP_local_file_header),1,file);
    

    但由于中间有空字节,所以无法正确读取。

    我能做些什么来顺序高效地存储数据 ZIP_local_file_header 不浪费字节?

    5 回复  |  直到 14 年前
        1
  •  9
  •   Adrian Smith    14 年前

    C struct S只是将相关的数据块分组在一起,它们 不要在内存中指定特定的布局 . (就像 int 也没有定义。)小endian/big endian也没有定义,这取决于处理器。

    不同的编译器、不同体系结构或操作系统上的相同编译器等,所有布局结构都会有所不同。

    由于要读取的文件格式是根据字节的去向来定义的,因此结构虽然看起来非常方便和诱人,但不是正确的解决方案。您需要将文件视为 char[] 然后拉出所需的字节并移动它们,以使数字由多个字节组成,等等。

        2
  •  10
  •   John Bode    14 年前

    为了满足底层平台的对齐要求,结构在成员之间可以有“padding”字节,以便每个成员从正确对齐的地址开始。

    有几种方法可以解决这个问题:一种是使用适当大小的成员分别读取头的每个元素:

    fread(&p.signature, sizeof p.signature, 1, file);
    fread(&p.version, sizeof p.version, 1, file);
    ...
    

    另一个是使用 位字段 在结构定义中;这些不受填充限制。缺点是位字段必须 unsigned int int 或者,如C99, _Bool ;您可能需要将原始数据强制转换为目标类型才能正确解释它:

    typedef struct {                 
        unsigned int signature          : 32;
        unsigned int version            : 16;                
        unsigned int bit_flag;          : 16;                
        unsigned int compression_method : 16;              
        unsigned int time               : 16;
        unsigned int date               : 16;
        unsigned int crc                : 32;
        unsigned int compressed_size    : 32;                 
        unsigned int uncompressed_size  : 32;
        unsigned int name_length        : 16;    
        unsigned int extra_field_length : 16;
    } ZIP_local_file_header;
    

    如果文件是用big-endian写的,那么您可能还需要在每个成员中进行一些字节交换,但是您的系统是小endian。

    注意 name extra field 不是结构定义的一部分;当您从文件中读取时,将不会读取 指针 名称和额外字段的值,您将要读取 实际内容 名称和额外字段。由于您不知道这些字段的大小,直到您读取了头的其余部分,所以您应该将这些字段的读取推迟到您读取了上面的结构之后。类似的东西

    ZIP_local_file_header p;
    char *name = NULL;
    char *extra = NULL;
    ...
    fread(&p, sizeof p, 1, file);
    if (name = malloc(p.name_length + 1))
    {
        fread(name, p.name_length, 1, file);
        name[p.name_length] = 0;
    }
    if (extra = malloc(p.extra_field_length + 1))
    {
        fread(extra, p.extra_field_length, 1, file);
        extra[p.extra_field_length] = 0;
    }
    
        3
  •  5
  •   Oliver Charlesworth    14 年前

    解决方案是特定于编译器的,但例如在gcc中,您可以通过附加 __attribute__((packed)) 根据定义。见 http://gcc.gnu.org/onlinedocs/gcc-3.2.3/gcc/Type-Attributes.html .

        4
  •  2
  •   ExitToShell    14 年前

    我已经有一段时间没有处理压缩文件了,但是我确实记得添加自己的填充来实现PowerPCarch的4字节对齐规则的实践。

    充其量,您只需将结构的每个元素定义为您想要读入的数据块的大小。不要只使用“int”,因为它可能是定义为各种大小的平台/编译器。

    在标题中执行类似的操作:

    typedef unsigned long   unsigned32;
    typedef unsigned short  unsigned16;
    typedef unsigned char   unsigned8;
    typedef unsigned char   byte;
    

    然后使用一个非签名的32代替int,其中有一个已知的4字节的变量。对于任何已知的2字节值,无符号16。

    这将帮助您了解在哪里可以添加填充字节以实现4字节对齐,或者在哪里可以对2个2字节的元素进行分组以组成4字节对齐。

    理想情况下,您可以使用最小的填充字节(可以在以后扩展程序时用于添加其他数据),如果您可以将所有内容与末尾的可变长度数据对齐到4字节边界,则可以完全不使用填充字节。

        5
  •  0
  •   Prof. Falken    14 年前

    此外,名称和额外的_字段可能不会包含任何有意义的数据。至少不在程序运行之间,因为这些都是指针。