2010-11-14 121 views
1

我一直在使用C/C++最近加載了很多二進制文件,我很困擾它可能會有多不雅。無論我得到了很多的代碼看起來像這樣(我後來又跳槽):C中優雅的二進制I/O?

uint32_t type, k; 
uint32_t *variable; 
FILE *f; 

if (!fread(&type, 4, 1, f)) 
    goto boundsError; 

if (!fread(&k, 4, 1, f)) 
    goto boundsError; 

variable = malloc(4 * k); 
if (!fread(variable, 4 * k, 1, f)) 
    goto boundsError; 

或者,我定義了一個地方,包裝結構,這樣我可以在固定大小的塊更容易閱讀。然而,在我看來,對於這樣一個簡單的問題 - 即將指定的文件讀入內存 - 可以更有效地以更可讀的方式完成。有沒有人有任何提示/技巧等?我想澄清一點,我不是在尋找一個圖書館或某些東西來處理這個問題;如果我正在設計自己的文件,並且必須更改文件規格,我可能會很感興趣,但現在我只是在尋找文體上的答案。

另外,你們中的一些人可能會建議mmap -I love mmap!我使用它很多,但問題在於,它導致處理未對齊數據類型的令人討厭的代碼,這在使用stdio時並不存在。最後,我會編寫stdio-like封裝函數來從內存中讀取數據。

謝謝!

編輯:我還應該澄清,我不能更改文件格式 - 有一個二進制文件,我必須閱讀;我無法以其他格式請求數據。

+0

'mmap()的' !與'union'一起處理未對齊的數據訪問。 – 2010-11-14 01:56:35

+0

我希望我能做到,但性能命中值得嗎?這是避免它的最初意圖。我的意思是,說實話,無論如何,我可能會在不知情的情況下在我的'FILE'緩衝區中引起未對齊的內存訪問。 – duane 2010-11-14 02:11:42

+1

您是否真的評估過性能? x86上的未對齊訪問相對便宜。 – Chris 2010-11-14 03:08:39

回答

1

如果您想對二進制數據進行反序列化,一種選擇是爲要使用的結構定義序列化宏。這是一個lot更容易在C++中使用模板函數和流。 (提高::序列化是一種非介入式序列庫,但如果你想要去打擾,你可以把它更優雅)

簡單的C宏:

#define INT(f,v) \ 
    { int _t; fread(&_t, sizeof(int), 1, f); v = ntohl(_t); } 
#define FLOAT(f,v) \ 
    { int _t; fread(&_t, sizeof(int), 1, f); v = ntohl(_t); /* type punning */ memcpy(&v, &_t, sizeof(float)); } 
... 

用法:

int a; 
    float b; 
    FILE *f = fopen("file", "rb"); 

    INT(f, a); 
    FLOAT(f, b); 

而且,是的,序列化代碼是一些最無聊和腦死的代碼。如果可以的話,使用元數據來描述你的數據結構,而不是機械地生成代碼。有一些工具和庫可以幫助解決這個問題,或者你可以用Perl,Python或PowerShell或其他方式推出自己的工具。

+0

這裏沒有什麼需要一個宏,而是使用內聯函數。 – 2010-11-14 02:39:52

+0

此外,在浮點值的二進制表示形式上調用'ntohl'是錯誤的... IEEE浮點表示法是標準化的,不會隨着機器字節順序而變化。 – 2010-11-14 02:52:51

+0

C中的內聯函數不能按類型重載,並且你沒有引用語義,所以你必須添加一個「&」,這又創建了字段/變量的別名。或者您必須使用價值返回調用約定。這是一種不同的風格。 – 2010-11-14 03:03:08

-1

您可能會感興趣protocol buffers和其他IDL方案。

+0

我也會這樣建議,但OP似乎不想要第三方庫。它具有在同一平臺上的不同編譯器,不同平臺和相同編譯器的不同版本之間非常明確的可移植性。 – Omnifarious 2010-11-14 01:57:17

+1

我很喜歡這一點,但不幸的是我受到限制,我不會選擇輸入文件格式。 – duane 2010-11-14 02:27:38

3

我見過這個問題的最優雅的解決方案是肖恩巴雷特的writefv,用在他的小圖像寫作庫stb_image_write可用here。他只實現了幾個原語(並且沒有錯誤處理),但是相同的方法可以擴展到基本上是二進制的printf(並且對於閱讀,可以執行相同的操作來獲得二進制scanf)。非常優雅和整潔!事實上,整個事情就是這麼簡單,我也可能包括在這裏:

static void writefv(FILE *f, const char *fmt, va_list v) 
{ 
    while (*fmt) { 
     switch (*fmt++) { 
     case ' ': break; 
     case '1': { unsigned char x = (unsigned char) va_arg(v, int); fputc(x,f); break; } 
     case '2': { int x = va_arg(v,int); unsigned char b[2]; 
        b[0] = (unsigned char) x; b[1] = (unsigned char) (x>>8); 
        fwrite(b,2,1,f); break; } 
     case '4': { stbiw_uint32 x = va_arg(v,int); unsigned char b[4]; 
        b[0]=(unsigned char)x; b[1]=(unsigned char)(x>>8); 
        b[2]=(unsigned char)(x>>16); b[3]=(unsigned char)(x>>24); 
        fwrite(b,4,1,f); break; } 
     default: 
      assert(0); 
      return; 
     } 
    } 
} 

,這裏是使用它他是怎麼寫的真彩色.BMP文件:

static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, void *data, int alpha, int pad, const char *fmt, ...) 
{ 
    FILE *f; 
    if (y < 0 || x < 0) return 0; 
    f = fopen(filename, "wb"); 
    if (f) { 
     va_list v; 
     va_start(v, fmt); 
     writefv(f, fmt, v); 
     va_end(v); 
     write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad); 
     fclose(f); 
    } 
    return f != NULL; 
} 

int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) 
{ 
    int pad = (-x*3) & 3; 
    return outfile(filename,-1,-1,x,y,comp,(void *) data,0,pad, 
      "11 4 22 4" "4 44 22 444444", 
      'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header 
      40, x,y, 1,24, 0,0,0,0,0,0);    // bitmap header 
} 

(的write_pixels定義消隱因爲它在這裏非常相切)

+0

誘人,但相當不可讀。 – duane 2010-11-14 02:08:11

0

我會通過重構一下你的代碼來減少你的代碼,所以你的複雜數據結構可以通過一系列的底層類型的調用來讀取。

我假設你的代碼是純C而不是C++,因爲在後者中你可能會拋出異常而不是使用goto語句。

+0

我不明白重構如何使它更優雅 - 事實上,它會使代碼更混亂。除非我「解析」一個子結構(這確實發生了)或跨段,否則我寧願不分割我的代碼。 – duane 2010-11-14 02:24:41

0

數組讀取部分看起來應該有自己的可重用函數。除此之外,如果你確實有C++可用(這個問題還沒有完全清楚),那麼硬編碼變量的大小是不必要的,因爲大小可以從指針中推導出來。

template<typename T> 
bool read(FILE* const f, T* const p, size_t const n = 1) 
{ 
    return n * sizeof(T) == fread(f, sizeof T, n, p); 
} 

template<typename T> 
bool read(FILE* const f, T& result) 
{ 
    return read(f, &result); 
} 

template<typename Tcount, typename Telement> 
bool read_counted_array(FILE* const f, Tcount& n, Telement*& p) 
{ 
    if (!read(f, n) || !(p = new Telement[n])) 
     return false; 
    if (read(f, p, n)) 
     return true; 
    delete[] p; 
    p = 0; 
    return false; 
} 

然後

uint32_t type, k; 
uint32_t *variable; 
FILE *f; 

if (read(f, type) && 
    read_counted_array(f, k, variable) && ... 
    ) { 
    //... 
} 
else 
    goto boundsError; 

當然,隨時可以繼續使用mallocfree代替new[]delete[]數據是否越區切換到代碼假設malloc使用。

0

這是我想出了一些C99代碼:

你的榜樣將是:

#include "read_values.h" 
#include "read_array.h" 

assert(sizeof (uint32_t) == 4); 

uint32_t type, k; 
uint32_t *variable; 
FILE *f; 

_Bool success = 
    read_values(f, "c4c4", &type, &k) && 
    read_array(f, variable, k); 

if(!success) 
{ 
    /* ... */ 
}