1

我最近一直在使用 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 的包装函数来从内存中读取。

谢谢!

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

4

6 回答 6

3

对于这个问题,我见过的最优雅的解决方案是 Sean Barrett 的writefv,用于他的微型图像编写库stb_image_write,可在此处获得。他只实现了一些原语(并且没有错误处理),但同样的方法可以扩展到基本上是二进制文件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省略的定义,因为它在这里很切线)

于 2010-11-14T02:00:53.167 回答
1

如果要反序列化二进制数据,一种选择是为要使用的结构定义序列化宏。这在 C++ 中使用模板函数和流要容易得多。(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 或其他任何东西中使用自己的工具和库。

于 2010-11-14T02:20:06.993 回答
0

我会通过对其进行一些重构来使您的代码看起来不那么优雅,因此您的复杂数据结构可以通过一系列对其基础类型的调用来读取。

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

于 2010-11-14T02:19:59.760 回答
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;

当然,如果数据被移交给假设已使用的代码,请随意继续使用andmallocfree不是。new[]delete[]malloc

于 2010-11-14T02:51:01.113 回答
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)
{
    /* ... */
}
于 2010-11-14T13:36:50.690 回答
-1

您可能对协议缓冲区和其他 IDL 方案感兴趣。

于 2010-11-14T01:54:48.067 回答