2010-10-21 166 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字節邊界對齊,結束。