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;

または、ローカルのパック構造体を定義して、一定サイズのブロックを簡単に読み取れるようにします。ただし、このような単純な問題、つまり、指定されたファイルをメモリに読み込む場合は、より効率的に、より読みやすい方法で実行できるように思えます。ヒント/コツなどはありますか?これを処理するためのライブラリや何かを探しているわけではないことを明確にしたいと思います。自分のファイルを設計していて、ファイルの仕様を大幅に変更しなければならないとしたら、と思うかもしれませんが、今のところはスタイル上の答えを探しているだけです。

また、私はmmapmmap が大好きです! 私はそれをよく使用しますが、問題は、標準入出力を使用する場合には実際には存在しない、整列されていないデータ型を処理するための厄介なコードにつながることです。最終的には、メモリから読み取るための stdio のようなラッパー関数を作成することになります。

ありがとう!

編集:ファイル形式を変更できないことも明確にする必要があります。読み取る必要があるバイナリ ファイルがあります。別の形式でデータを要求することはできません。

4

6 に答える 6

3

この問題に対して私がこれまでに見た中で最も洗練された解決策は、Sean Barrett のであり、ここで利用可能なwritefv彼の小さな画像書き込みライブラリで使用されています。彼はいくつかのプリミティブのみを実装しています (エラー処理はありません) が、同じアプローチを基本的にバイナリに拡張できます(読み取りの場合は、バイナリを取得するために同じことを行うことができます)。とてもエレガントで整頓されています!実際、全体は非常に単純なので、ここに含めることもできます。stb_image_writeprintfscanf

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

バイナリ データを逆シリアル化する場合、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);

そして、そうです、シリアライゼーション コードは、書くのが最も退屈で脳死状態のコードの 1 つです。可能であれば、メタデータを使用してデータ構造を記述し、代わりに機械的にコードを生成してください。これを支援するツールとライブラリがあります。または、Perl、Python、PowerShell などで独自のツールを展開することもできます。

于 2010-11-14T02:20:06.993 に答える
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;

もちろん、データが使用されたと想定するコードにデータが渡される場合は、代わりに使用mallocを継続してください。freenew[]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 に答える
0

コードを少しリファクタリングすることで、コードの見栄えを良くすることができます。そのため、複雑なデータ構造は、その基礎となる型の一連の呼び出しで読み取られます。

あなたのコードは純粋な C であり、C++ ではないと仮定します。後者では、goto ステートメントを使用するのではなく、おそらく例外をスローするからです。

于 2010-11-14T02:19:59.760 に答える
-1

プロトコルバッファやその他のIDLスキームに興味があるかもしれません。

于 2010-11-14T01:54:48.067 に答える