2010-10-21 162 views
12

我正在从文件读取二进制数据,特别是从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浪费无字节?

+0

http://stackoverflow.com/questions/3913119/dumping-memory-to-file/3913152#3913152 < - 可能的重复 – 2010-10-21 14:52:30

+3

写得很好的问题。 – 2010-10-21 14:54:36

回答

9

C struct s只是关于将相关数据组合在一起,他们没有指定内存中的特定布局。 (就像int的宽度也没有定义一样。)Little-endian/Big-endian也没有定义,并且取决于处理器。

不同的编译器,在不同的体系结构或操作系统等相同的编译器,将所有的布局结构不同。

当你想阅读其中的条款定义的文件格式字节去哪里,一个结构,虽然看起来很方便的和诱人的,是不是正确的解决方案。您需要将该文件视为char[],并提取需要的字节并将它们移动以便制作由多个字节组成的数字等。

+0

+1用于提出便携式解决方案。 – 2010-10-21 14:56:03

+0

这是我的解决方案。但它使阅读更加复杂并且依赖于结构。 – rigon 2010-10-21 15:06:51

+7

结构成员将按照它们声明的顺序进行布局。从6.7.2.1的第13段开始:“在一个结构对象中,非位域成员和位域 所在的单元的地址增加了它们被声明的顺序*。指向经过适当转换的结构对象指向其初始成员(或者如果该成员是位域,则返回其所在的单位),反之亦然。在结构对象中可能有未命名的填充,但不在其开始处。“重点是我的。 – 2010-10-21 16:12:37

0

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

+0

我知道,但我的问题是因为我有5'短整型'和分配的内存是8个字节,但只有6个被使用。 – rigon 2010-10-21 14:58:45

9

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

有解决此几种方法:一种是读取头中的每个元素分别用适当尺寸的部件:

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

另一种是使用在你的结构定义位字段;这些不受填充限制。缺点是位字段必须是unsigned intint或者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; 

你也可能需要做的每一个成员的一些字节交换,如果该文件是写在大端但系统小端。

请注意,nameextra 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; 
} 
+0

非常好的解释。但是如果我将结构的指针传递给函数并使用该字段的地址,我遇到了一个错误: zip.c:42:2:错误:无法获取位字段'签名'的地址 zip.c:42:2:错误:'sizeof'已应用到一个位字段 – rigon 2010-10-21 18:39:41

+2

@Ricardo - 你应该传递指向结构成员的指针,如* *原始*结构类型中定义**或**使用位域并传递整个结构的地址,你不能把地址领域。 – 2010-10-21 19:08:43

2

这已经有一段时间,因为我用ZIP压缩文件的工作,但我记得我加入填充自己打的PowerPC拱的4字节对齐规则的做法。

最好你只需要将你的结构的每个元素定义为你想要读取的数据的大小。不要只用'int',因为这可能是平台/编译器定义的各种大小。

做这样的事情在一个头:

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

然后,而不是只用INT,你有一个已知的4字节vaule的UNSIGNED32。无符号16表示任何已知的2字节值。

这将帮助您查看可以在哪里添加填充字节以打4字节对齐方式,或者您可以在哪里组合2个2字节元素以组成4字节对齐方式。

理想情况下,您可以使用最少的填充字节(可用于在扩展程序后添加其他数据),或者根本不使用填充字节,如果您可以将所有内容与4字节边界对齐,结束。