将二进制数据(从文件)读入结构
我正在从文件中读取二进制数据,特别是从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+nm Extra field */ } ZIP_local_file_header;
sizeof(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
浪费无字节,我可以做些什么来顺序有效地存储数据?
C struct
只是将相关的数据分组在一起,它们没有在内存中指定特定的布局 。 (正如int
的宽度也没有定义。)Little-endian / Big-endian也没有定义,并且取决于处理器。
不同的编译器,不同体系结构或操作系统上的相同编译器等,将以不同的方式布局结构。
由于要读取的文件格式是根据哪个字节去哪里定义的,因此结构虽然看起来非常方便和诱人,但却不是正确的解决方案。 您需要将文件视为char[]
并拉出所需的字节并移动它们以使数字由多个字节组成,等等。
为了满足底层平台的对齐要求,结构可以在成员之间具有“填充”字节,以便每个成员从正确对齐的地址开始。
有几种方法可以解决这个问题:一种方法是使用适当大小的成员分别读取标题的每个元素:
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编写的,但你的系统是little-endian,你可能还需要在每个成员中进行一些字节交换。
请注意, name
和extra field
不是struct定义的一部分; 当你从文件中读取时,你不会读取名称和额外字段的指针值,你将会读取名称和额外字段的实际内容 。 由于在阅读标题的其余部分之前您不知道这些字段的大小,因此在阅读上述结构之后,您应该推迟阅读它们。 就像是
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; }
该解决方案是特定于编译器的,但是例如在GCC中,您可以通过将__attribute__((packed))
附加到定义来强制它更紧密地打包结构。 请参阅http://gcc.gnu.org/onlinedocs/gcc-3.2.3/gcc/Type-Attributes.html 。
自从我使用zip压缩文件以来已经有一段时间了,但我确实记得添加自己的填充以实现PowerPC arch的4字节对齐规则的做法。
最好你只需要将结构的每个元素定义为你想要读入的数据块的大小。不要只使用’int’,因为它可能是平台/编译器定义为各种大小。
在标题中执行以下操作:
typedef unsigned long unsigned32; typedef unsigned short unsigned16; typedef unsigned char unsigned8; typedef unsigned char byte;
然后使用unsigned32代替仅使用int,其中有一个已知的4字节vaule。 对于任何已知的2字节值,unsigned16。
这将帮助您查看在哪里可以添加填充字节以达到4字节对齐,或者您可以将2,2字节元素组合在一起以构成4字节对齐。
理想情况下,您可以使用最少的填充字节(可以在以后扩展程序时用于添加其他数据),或者根本不使用任何填充字节,如果您可以将所有内容与最后的可变长度数据的4字节边界对齐。
此外,名称和extra_field最有可能不包含任何有意义的数据。 至少不在程序运行之间,因为这些是指针。