C中优雅的二进制i / o?

时间:2010-11-14 01:48:09

标签: c++ c binary io stdio

我最近使用C / C ++加载了很多二进制文件,而且我对它的优雅程度感到困惑。要么我得到很多看起来像这样的代码(我已经开始了):

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 - 我喜欢mmap!我经常使用它,但它的问题在于它导致了处理未对齐数据类型的讨厌代码,这在使用stdio时并不存在。最后,我将编写类似stdio的包装函数,用于从内存中读取。

谢谢!

编辑:我还应该澄清一下,我无法更改文件格式 - 我必须阅读一个二进制文件;我无法以其他格式请求数据。

6 个答案:

答案 0 :(得分:3)

我见过这个问题的最优雅的解决方案是Sean Barrett的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;
      }
   }
}

以下是他如何使用它编写truecolor .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的定义已被删除,因为它在这里很切线)

答案 1 :(得分:1)

如果要反序列化二进制数据,可以选择为要使用的结构定义序列化宏。使用模板函数和流,C ++中的 lot 更容易。 (boost :: serialization是一个非侵入式序列化库,但如果你想要打扰,你可以让它更优雅)

简单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或其他任何方式自行编写。

答案 2 :(得分:0)

我会通过重构一下来使你的代码看起来不那么优雅,所以你的复杂数据结构会通过一系列基础类型的调用来读取。

我假设您的代码是纯C而不是C ++,因为在后者中您可能会抛出异常而不是使用goto语句。

答案 3 :(得分: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;

当然,如果将数据传递给假定{{1}的代码,请随时继续使用mallocfree代替new[]delete[]使用了。

答案 4 :(得分: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)
{
    /* ... */
}

答案 5 :(得分:-1)

您可能对protocol buffers和其他IDL计划感兴趣。