2010-10-21 39 views
12

Tôi đang đọc dữ liệu nhị phân từ tệp, cụ thể là từ tệp zip. (Để biết thêm về cấu trúc định dạng zip thấy http://en.wikipedia.org/wiki/ZIP_%28file_format%29)Đọc dữ liệu nhị phân (từ tệp) thành cấu trúc

tôi đã tạo ra một cấu trúc lưu trữ dữ liệu:

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; 

Kích thước được trả về bởi sizeof(ZIP_local_file_header) là 40, nhưng nếu tổng của từng lĩnh vực là tính toán với sizeof điều hành tổng kích thước là 38.

Nếu chúng ta có cấu trúc tiếp theo:

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

sizeof(FOO) trả về 8 vì bộ nhớ được cấp phát với 4 byte mỗi lần. Vì vậy, để phân bổ x được dành riêng 4 byte (nhưng kích thước thực là 2 byte). Nếu chúng ta cần thêm short int, nó sẽ lấp đầy 2 byte còn lại của phân bổ trước đó. Nhưng khi chúng tôi có một số int, nó sẽ được phân bổ cộng 4 byte và 2 byte trống sẽ bị lãng phí.

Để đọc dữ liệu từ tập tin, chúng ta có thể sử dụng chức năng fread:

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

Nhưng vì có đang byte rỗng ở giữa, nó không phải là đọc một cách chính xác.

Tôi có thể làm gì để lưu trữ dữ liệu tuần tự và hiệu quả với ZIP_local_file_header lãng phí không có byte?

+0

http://stackoverflow.com/questions/3913119/dumping-memory-to-file/3913152#3913152 <- có thể trùng lặp –

+3

Câu hỏi được viết độc đáo. –

Trả lời

9

C struct s chỉ là về nhóm các phần dữ liệu có liên quan với nhau, chúng không chỉ định bố cục cụ thể trong bộ nhớ. (Cũng giống như chiều rộng của int cũng không được xác định.) Little-endian/Big-endian cũng không được xác định và phụ thuộc vào bộ xử lý.

Trình biên dịch khác nhau, cùng một trình biên dịch trên các kiến ​​trúc hoặc hệ điều hành khác nhau, v.v., tất cả các cấu trúc bố cục sẽ khác nhau.

Khi định dạng tệp bạn muốn đọc được xác định theo byte nào đi đâu, cấu trúc, mặc dù trông rất thuận tiện và hấp dẫn, không phải là giải pháp phù hợp. Bạn cần xử lý tệp dưới dạng char[] và kéo ra các byte bạn cần và thay đổi chúng để tạo số gồm nhiều byte, v.v.

+0

+1 để đề xuất giải pháp di động. –

+0

Đó là giải pháp mà tôi có. Nhưng nó làm cho việc đọc phức tạp hơn và phụ thuộc vào cấu trúc. – rigon

+7

thành viên cấu trúc sẽ được sắp xếp theo thứ tự mà chúng được khai báo. Từ 6.7.2.1, đoạn 13: "Trong một đối tượng cấu trúc, các thành phần không bit-bit và các đơn vị trong đó các trường bit có các địa chỉ * tăng theo thứ tự mà chúng được khai báo *. đối tượng cấu trúc, được chuyển đổi phù hợp, trỏ đến thành viên ban đầu của nó (hoặc nếu thành viên đó là một trường bit , sau đó đến đơn vị nơi nó cư trú) và ngược lại.Có thể có đệm không tên trong một đối tượng cấu trúc, nhưng không phải lúc bắt đầu. "Nhấn mạnh mỏ. –

5

Giải pháp là trình biên dịch cụ thể, nhưng ví dụ trong GCC, bạn có thể buộc nó đóng gói cấu trúc chặt chẽ hơn bằng cách thêm __attribute__((packed)) vào định nghĩa. Xem http://gcc.gnu.org/onlinedocs/gcc-3.2.3/gcc/Type-Attributes.html.

+0

Tôi đã tự thực hiện định dạng zip và điều này giải quyết được vấn đề trong trường hợp của tôi. – Peterdk

0

Ngoài ra, tên và extra_field sẽ không chứa bất kỳ dữ liệu có ý nghĩa nào, rất có thể. Ít nhất không phải giữa các lần chạy chương trình, vì đây là các con trỏ.

+0

Tôi biết điều đó, nhưng vấn đề của tôi là vì tôi có 5 'int' ngắn và bộ nhớ được phân bổ là 8 byte, nhưng chỉ 6 được sử dụng. – rigon

9

Để đáp ứng yêu cầu căn chỉnh của nền tảng cơ bản, cấu trúc có thể có "đệm" byte giữa các thành viên để mỗi thành viên bắt đầu tại một địa chỉ phù hợp đúng.

Có một số cách xung quanh này: một là để đọc mỗi yếu tố của tiêu đề riêng biệt bằng cách sử dụng thành viên kích thước thích hợp:

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

khác là sử dụng các lĩnh vực chút trong định nghĩa struct của bạn; những điều này không phụ thuộc vào các hạn chế về padding. Nhược điểm là các trường bit phải là unsigned int hoặc int hoặc, theo C99, _Bool; bạn có thể phải đúc các dữ liệu thô để loại mục tiêu để giải thích nó một cách chính xác:

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; 

Bạn cũng có thể phải làm một số byte-trao đổi trong mỗi thành viên nếu các tập tin được viết bằng lớn về cuối nhỏ nhưng hệ thống của bạn little-endian.

Lưu ý rằng nameextra field không thuộc định nghĩa cấu trúc; khi bạn đọc từ tệp, bạn sẽ không đọc con trỏ giá trị cho tên và trường bổ sung, bạn sẽ đọc nội dung thực tế của tên và trường bổ sung. Vì bạn không biết kích thước của các trường đó cho đến khi bạn đọc phần còn lại của tiêu đề, bạn nên hoãn đọc chúng cho đến khi bạn đã đọc cấu trúc ở trên. Một cái gì đó như

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

Giải thích rất tốt. Nhưng nếu tôi chuyển một con trỏ từ cấu trúc đến chức năng và sử dụng địa chỉ của trường, tôi gặp lỗi: zip.c: 42: 2: lỗi: không thể lấy địa chỉ của trường bit 'chữ ký' zip.c: 42: 2: lỗi: 'sizeof' được áp dụng vào một trường bit – rigon

+2

@Ricardo - bạn nên chuyển con trỏ tới các thành viên cấu trúc như được định nghĩa trong kiểu * nguyên bản * struct ** hoặc ** sử dụng bitfields của bạn và chuyển địa chỉ của toàn bộ cấu trúc. Bạn không thể lấy địa chỉ của một chút cánh đồng. –

2

Đã một thời gian kể từ khi tôi làm việc với các tệp nén, nhưng tôi nhớ thực hành thêm phần đệm của riêng mình để đạt các quy tắc căn chỉnh 4 byte của PowerPC. Tốt nhất bạn chỉ cần xác định từng phần tử của cấu trúc của bạn với kích thước của đoạn dữ liệu bạn muốn đọc. Không chỉ sử dụng 'int' vì đó có thể là nền tảng/trình biên dịch được định nghĩa với các kích cỡ khác nhau.

Do something như thế này trong một tiêu đề:

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

Sau đó, thay vì chỉ sử dụng một int unsigned32, nơi bạn có một tiếng vaule 4-byte. Và unsigned16 cho bất kỳ giá trị 2 byte nào đã biết.

Điều này sẽ giúp bạn biết nơi bạn có thể thêm byte đệm để nhấn căn chỉnh 4 byte hoặc nơi bạn có thể nhóm 2, 2 byte thành phần căn chỉnh 4 byte. Lý tưởng nhất là bạn có thể sử dụng tối thiểu các byte đệm (có thể được sử dụng để thêm dữ liệu bổ sung sau này khi mở rộng chương trình) hoặc không có gì nếu bạn có thể căn chỉnh mọi thứ thành ranh giới 4 byte với dữ liệu có độ dài thay đổi tại kết thúc.

Các vấn đề liên quan